delorie.com/archives/browse.cgi   search  
Mail Archives: cygwin/2006/10/13/16:47:38

X-Spam-Check-By: sourceware.org
Message-ID: <452FFB49.1060005@cdvinc.com>
Date: Fri, 13 Oct 2006 13:47:05 -0700
From: Tim Beuman <timb AT cdvinc DOT com>
User-Agent: Thunderbird 1.5.0.7 (Windows/20060909)
MIME-Version: 1.0
To: cygwin AT cygwin DOT com
Subject: ssh-agent not working with McAfee: a workaround with modified ssh-agent
X-Spam-Processed: dc.cdvinc.com, Fri, 13 Oct 2006 13:47:10 -0700 (not processed: message from valid local sender)
X-Return-Path: timb AT cdvinc DOT com
X-Envelope-From: timb AT cdvinc DOT com
X-MDaemon-Deliver-To: cygwin AT cygwin DOT com
X-IsSubscribed: yes
Mailing-List: contact cygwin-help AT cygwin DOT com; run by ezmlm
List-Subscribe: <mailto:cygwin-subscribe AT cygwin DOT com>
List-Archive: <http://sourceware.org/ml/cygwin/>
List-Post: <mailto:cygwin AT cygwin DOT com>
List-Help: <mailto:cygwin-help AT cygwin DOT com>, <http://sourceware.org/ml/#faqs>
Sender: cygwin-owner AT cygwin DOT com
Mail-Followup-To: cygwin AT cygwin DOT com
Delivered-To: mailing list cygwin AT cygwin DOT com
Note-from-DJ: This may be spam

--------------040200080800090601020602
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Hi,

I made a modified version of ssh-agent so it can run under cygrunsrv
under the local system account. This seems to work fine without having
to disable or uninstall McAfee.

Note: this is not an official update. It is a version I made for my own
purposes but others might find it usable too.

Changes I made:
1) Added a -r flag which prevents ssh-agent from forking.
2) Replaced fprintf(stderr,...) with logit(...) for logging to the event
log when running under cygrunsrv.
3) Added a source file (ssh-agent-reg.c) which contains a routine to
register the SSH_AUTH_SOCK in the global environment variable list of
windows (through the windows registry).
4) Qualified all identity(key) accesses with the euid/egid as presented
when adding the key initially.
Reason: when running ssh-agent under the SYSTEM account, all keys are
potentially accessible for everyone (security issue). Solution: when
adding a key, the euid and egid as obtained from getpeereid() are stored
together with the key. All subsequent accesses to that stored key are
restricted to the user which added the key (based on the euid and egid
obtained using getpeereid()). This basically limits access to the keys
to their creators only.
5) Added TAILQ_FOREACH_SAFE macro to openbsd-compat/sys-queue.h (needed
to remove only the keys for the invoking user when using ssh-add -D).

Notes:
Changes 1 and 4 are conditional compiled using #ifdef __CYGWIN__/#endif.
Change 3 is based on a solution that Karl M posted earlier (subject:
"Re: how to make ssh-agent automatically").

The changes are based on the source code of openssh-4.4p1-1.

I have tested these modifications on a W2K machine only. The registry
functions should work for Windows 95 upwards (according to their
description on the Microsoft website) so potentially this modified
version should work under W95 an up also but I haven't verified that.


To install ssh-agent as service under cygrunsrv I use:

cygrunsrv -I ssh-agent -p /usr/bin/ssh-agent -a "-r" -o

To start the service:

either:

cygrunsrv -S ssh-agent

or:

net start ssh-agent


In my .bash_login I added:

ssh-add -l > /dev/null 2>&1
if [ $? -eq 1 ]; then
    ssh-add
fi


Attached the modified source files and the modified Makefile.

Tim


--------------040200080800090601020602
Content-Type: text/plain;
 name="ssh-agent-reg.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="ssh-agent-reg.c"

#ifdef __CYGWIN__
#define STRICT
#include <windows.h>
#include "includes.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include "log.h"

// MAX_PATH is defined as 260 in windef.h

int ssh_agent_reg(char *socket_name)
{
	HKEY			hKey;
	DWORD			dwSize, dwReturnValue;
	BYTE			nValue[1024];
	static CHAR		RegPath[MAX_PATH] = "";
  LRESULT status;

	strcpy(RegPath,
      "SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment");

	if ((status = RegOpenKeyEx(
          HKEY_LOCAL_MACHINE,
          RegPath,
          0,
          KEY_SET_VALUE,
          &hKey
          )))
	{
    error("RegOpenKeyEx: %lu", (unsigned long)status);
		return 1;
	}

  strcpy(nValue, socket_name);
	dwSize = strlen(nValue) + 1;

	if ((status = RegSetValueEx(
          hKey,
          "SSH_AUTH_SOCK",
          0,
          REG_SZ,
          (BYTE *)nValue,
          dwSize
          )))
	{
		error("RegSetValueEx (SSH_AUTH_SOCK): %lu", (unsigned long)status);
		return 1;
	}

	if ((status = RegCloseKey(
          hKey
          )))
	{
		error("RegCloseKey: %lu", (unsigned long)status);
    return 1;
	}

  SendMessageTimeout(
      HWND_BROADCAST,
      WM_SETTINGCHANGE,
      0,
      (LPARAM) "Environment",
      SMTO_ABORTIFHUNG,
      5000,
      &dwReturnValue
      );

	return 0;
}
#endif


--------------040200080800090601020602
Content-Type: text/plain;
 name="ssh-agent.c"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename="ssh-agent.c"

/* $OpenBSD: ssh-agent.c,v 1.152 2006/08/04 20:46:05 stevesk Exp $ */
/*
 * Author: Tatu Ylonen <ylo AT cs DOT hut DOT fi>
 * Copyright (c) 1995 Tatu Ylonen <ylo AT cs DOT hut DOT fi>, Espoo, Finland
 *                    All rights reserved
 * The authentication agent program.
 *
 * As far as I am concerned, the code I have written for this software
 * can be used freely for any purpose.  Any derived versions of this
 * software must be clearly marked as such, and if the derived work is
 * incompatible with the protocol description in the RFC file, it must be
 * called by a name other than "ssh" or "Secure Shell".
 *
 * Copyright (c) 2000, 2001 Markus Friedl.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "includes.h"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_UN_H
# include <sys/un.h>
#endif
#include "openbsd-compat/sys-queue.h"

#include <openssl/evp.h>
#include <openssl/md5.h>

#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

#include "xmalloc.h"
#include "ssh.h"
#include "rsa.h"
#include "buffer.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 <sys/prctl.h>	/* For prctl() and PR_SET_DUMPABLE */
#endif

typedef enum {
	AUTH_UNUSED,
	AUTH_SOCKET,
	AUTH_CONNECTION
} sock_type;

typedef struct {
	int fd;
	sock_type type;
	Buffer input;
	Buffer output;
	Buffer request;
#ifdef __CYGWIN__
	uid_t euid;
	gid_t egid;
#endif
} SocketEntry;

u_int sockets_alloc = 0;
SocketEntry *sockets = NULL;

typedef struct identity {
	TAILQ_ENTRY(identity) next;
	Key *key;
	char *comment;
	u_int death;
	u_int confirm;
#ifdef __CYGWIN__
	uid_t euid;
	gid_t egid;
#endif
} Identity;

typedef struct {
	int nentries;
	TAILQ_HEAD(idqueue, identity) idlist;
} Idtab;

/* private key table, one per protocol version */
Idtab idtable[3];

int max_fd = 0;
#ifdef __CYGWIN__
int r_flag = 0;
extern int ssh_agent_reg(char *socket_name);
#endif

/* pid of shell == parent of agent */
pid_t parent_pid = -1;

/* pathname and directory for AUTH_SOCKET */
char socket_name[MAXPATHLEN];
char socket_dir[MAXPATHLEN];

/* locking */
int locked = 0;
char *lock_passwd = NULL;

extern char *__progname;

/* Default lifetime (0 == forever) */
static int lifetime = 0;

static void
close_socket(SocketEntry *e)
{
	close(e->fd);
	e->fd = -1;
	e->type = AUTH_UNUSED;
#ifdef __CYGWIN__
  e->euid = 0;
  e->egid = 0;
#endif
	buffer_free(&e->input);
	buffer_free(&e->output);
	buffer_free(&e->request);
}

static void
idtab_init(void)
{
	int i;

	for (i = 0; i <=2; i++) {
		TAILQ_INIT(&idtable[i].idlist);
		idtable[i].nentries = 0;
	}
}

/* return private key table for requested protocol version */
static Idtab *
idtab_lookup(int version)
{
	if (version < 1 || version > 2)
		fatal("internal error, bad protocol version %d", version);
	return &idtable[version];
}

static void
free_identity(Identity *id)
{
	key_free(id->key);
	xfree(id->comment);
	xfree(id);
}

/* return matching private key for given public key */
static Identity *
#ifdef __CYGWIN__
lookup_identity(Key *key, int version, SocketEntry *e)
#else
lookup_identity(Key *key, int version)
#endif
{
	Identity *id;

	Idtab *tab = idtab_lookup(version);
	TAILQ_FOREACH(id, &tab->idlist, next) {
		if (key_equal(key, id->key))
#ifdef __CYGWIN__
      /* only if the euid and egid match */
      if (r_flag && (e->euid == id->euid) && (e->egid == id->egid))
#endif
			return (id);
	}
	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)
{
	Idtab *tab = idtab_lookup(version);
	Identity *id;
	Buffer msg;
#ifdef __CYGWIN__
  int nentries = 0;
#endif

	buffer_init(&msg);
	buffer_put_char(&msg, (version == 1) ?
	    SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER);
#ifdef __CYGWIN__
  /* figure out in advance how many entries we are returning */
	TAILQ_FOREACH(id, &tab->idlist, next) {
    if (r_flag && (e->euid == id->euid) && (e->egid == id->egid))
      nentries++;
  }
	buffer_put_int(&msg, nentries);
#else
	buffer_put_int(&msg, tab->nentries);
#endif
	TAILQ_FOREACH(id, &tab->idlist, next) {
#ifdef __CYGWIN__
    if (r_flag && (e->euid == id->euid) && (e->egid == id->egid)) {
#endif
		  if (id->key->type == KEY_RSA1) {
			  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->n);
		  } else {
			  u_char *blob;
			  u_int blen;
			  key_to_blob(id->key, &blob, &blen);
			  buffer_put_string(&msg, blob, blen);
			  xfree(blob);
		  }
		  buffer_put_cstring(&msg, id->comment);
#ifdef __CYGWIN__
    }
#endif
	}
	buffer_put_int(&e->output, buffer_len(&msg));
	buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg));
	buffer_free(&msg);
}

/* ssh1 only */
static void
process_authentication_challenge1(SocketEntry *e)
{
	u_char buf[32], mdbuf[16], session_id[16];
	u_int response_type;
	BIGNUM *challenge;
	Identity *id;
	int i, len;
	Buffer msg;
	MD5_CTX md;
	Key *key;

	buffer_init(&msg);
	key = key_new(KEY_RSA1);
	if ((challenge = BN_new()) == NULL)
		fatal("process_authentication_challenge1: BN_new failed");

	(void) buffer_get_int(&e->request);			/* ignored */
	buffer_get_bignum(&e->request, key->rsa->e);
	buffer_get_bignum(&e->request, key->rsa->n);
	buffer_get_bignum(&e->request, challenge);

	/* Only protocol 1.1 is supported */
	if (buffer_len(&e->request) == 0)
		goto failure;
	buffer_get(&e->request, session_id, 16);
	response_type = buffer_get_int(&e->request);
	if (response_type != 1)
		goto failure;

#ifdef __CYGWIN__
	id = lookup_identity(key, 1, e);
#else
	id = lookup_identity(key, 1);
#endif
	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)
			goto failure;

		/* The response is MD5 of decrypted challenge plus session id. */
		len = BN_num_bytes(challenge);
		if (len <= 0 || len > 32) {
			logit("process_authentication_challenge: bad challenge length %d", len);
			goto failure;
		}
		memset(buf, 0, 32);
		BN_bn2bin(challenge, buf + 32 - len);
		MD5_Init(&md);
		MD5_Update(&md, buf, 32);
		MD5_Update(&md, session_id, 16);
		MD5_Final(mdbuf, &md);

		/* Send the response. */
		buffer_put_char(&msg, SSH_AGENT_RSA_RESPONSE);
		for (i = 0; i < 16; i++)
			buffer_put_char(&msg, mdbuf[i]);
		goto send;
	}

failure:
	/* Unknown identity or protocol error.  Send failure. */
	buffer_put_char(&msg, SSH_AGENT_FAILURE);
send:
	buffer_put_int(&e->output, buffer_len(&msg));
	buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg));
	key_free(key);
	BN_clear_free(challenge);
	buffer_free(&msg);
}

/* ssh2 only */
static void
process_sign_request2(SocketEntry *e)
{
	u_char *blob, *data, *signature = NULL;
	u_int blen, dlen, slen = 0;
	extern int datafellows;
	int ok = -1, flags;
	Buffer msg;
	Key *key;

	datafellows = 0;

	blob = buffer_get_string(&e->request, &blen);
	data = buffer_get_string(&e->request, &dlen);

	flags = buffer_get_int(&e->request);
	if (flags & SSH_AGENT_OLD_SIGNATURE)
		datafellows = SSH_BUG_SIGBLOB;

	key = key_from_blob(blob, blen);
	if (key != NULL) {
#ifdef __CYGWIN__
	  Identity *id = lookup_identity(key, 2, e);
#else
		Identity *id = lookup_identity(key, 2);
#endif
		if (id != NULL && (!id->confirm || confirm_key(id) == 0))
			ok = key_sign(id->key, &signature, &slen, data, dlen);
		key_free(key);
	}
	buffer_init(&msg);
	if (ok == 0) {
		buffer_put_char(&msg, SSH2_AGENT_SIGN_RESPONSE);
		buffer_put_string(&msg, signature, slen);
	} else {
		buffer_put_char(&msg, SSH_AGENT_FAILURE);
	}
	buffer_put_int(&e->output, buffer_len(&msg));
	buffer_append(&e->output, buffer_ptr(&msg),
	    buffer_len(&msg));
	buffer_free(&msg);
	xfree(data);
	xfree(blob);
	if (signature != NULL)
		xfree(signature);
}

/* shared */
static void
process_remove_identity(SocketEntry *e, int version)
{
	u_int blen, bits;
	int success = 0;
	Key *key = NULL;
	u_char *blob;

	switch (version) {
	case 1:
		key = key_new(KEY_RSA1);
		bits = buffer_get_int(&e->request);
		buffer_get_bignum(&e->request, key->rsa->e);
		buffer_get_bignum(&e->request, key->rsa->n);

		if (bits != key_size(key))
			logit("Warning: identity keysize mismatch: actual %u, announced %u",
			    key_size(key), bits);
		break;
	case 2:
		blob = buffer_get_string(&e->request, &blen);
		key = key_from_blob(blob, blen);
		xfree(blob);
		break;
	}
	if (key != NULL) {
#ifdef __CYGWIN__
    Identity *id = lookup_identity(key, version, e);
#else
    Identity *id = lookup_identity(key, version);
#endif
		if (id != NULL) {
			/*
			 * We have this key.  Free the old key.  Since we
			 * 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.
			 */
			Idtab *tab = idtab_lookup(version);
			if (tab->nentries < 1)
				fatal("process_remove_identity: "
				    "internal error: tab->nentries %d",
				    tab->nentries);
			TAILQ_REMOVE(&tab->idlist, id, next);
			free_identity(id);
			tab->nentries--;
			success = 1;
		}
		key_free(key);
	}
	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output,
	    success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
}

static void
process_remove_all_identities(SocketEntry *e, int version)
{
	Idtab *tab = idtab_lookup(version);
	Identity *id, *tid;

	/* Loop over all identities and clear the keys. */
#ifdef __CYGWIN__
  if (r_flag && (tab->nentries > 0)) {
    /* system-wide database so remove only the keys */
    /* added by the user with the supplied euid/egid */
    TAILQ_FOREACH_SAFE(id, &tab->idlist, next, tid) {
      if ((e->euid == id->euid) && (e->egid == id->egid)) {
		    TAILQ_REMOVE(&tab->idlist, id, next);
        tab->nentries--;
      }
    }
  } else {
    /* assume a single-user database */
    /* removing all entries unconditionally */
#endif
	  for (id = TAILQ_FIRST(&tab->idlist); id;
	      id = TAILQ_FIRST(&tab->idlist)) {
		  TAILQ_REMOVE(&tab->idlist, id, next);
		  free_identity(id);
	  }

	  /* Mark that there are no identities. */
	  tab->nentries = 0;
#ifdef __CYGWIN__
  }
#endif

	/* Send success. */
	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output, SSH_AGENT_SUCCESS);
}

static void
reaper(void)
{
	u_int now = time(NULL);
	Identity *id, *nxt;
	int version;
	Idtab *tab;

	for (version = 1; version < 3; version++) {
		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) {
				TAILQ_REMOVE(&tab->idlist, id, next);
				free_identity(id);
				tab->nentries--;
			}
		}
	}
}

static void
process_add_identity(SocketEntry *e, int version)
{
	Idtab *tab = idtab_lookup(version);
	int type, success = 0, death = 0, confirm = 0;
	char *type_name, *comment;
	Key *k = NULL;

	switch (version) {
	case 1:
		k = key_new_private(KEY_RSA1);
		(void) buffer_get_int(&e->request);		/* ignored */
		buffer_get_bignum(&e->request, k->rsa->n);
		buffer_get_bignum(&e->request, k->rsa->e);
		buffer_get_bignum(&e->request, k->rsa->d);
		buffer_get_bignum(&e->request, k->rsa->iqmp);

		/* SSH and SSL have p and q swapped */
		buffer_get_bignum(&e->request, k->rsa->q);	/* p */
		buffer_get_bignum(&e->request, k->rsa->p);	/* q */

		/* Generate additional parameters */
		rsa_generate_additional_parameters(k->rsa);
		break;
	case 2:
		type_name = buffer_get_string(&e->request, NULL);
		type = key_type_from_name(type_name);
		xfree(type_name);
		switch (type) {
		case KEY_DSA:
			k = key_new_private(type);
			buffer_get_bignum2(&e->request, k->dsa->p);
			buffer_get_bignum2(&e->request, k->dsa->q);
			buffer_get_bignum2(&e->request, k->dsa->g);
			buffer_get_bignum2(&e->request, k->dsa->pub_key);
			buffer_get_bignum2(&e->request, k->dsa->priv_key);
			break;
		case KEY_RSA:
			k = key_new_private(type);
			buffer_get_bignum2(&e->request, k->rsa->n);
			buffer_get_bignum2(&e->request, k->rsa->e);
			buffer_get_bignum2(&e->request, k->rsa->d);
			buffer_get_bignum2(&e->request, k->rsa->iqmp);
			buffer_get_bignum2(&e->request, k->rsa->p);
			buffer_get_bignum2(&e->request, k->rsa->q);

			/* Generate additional parameters */
			rsa_generate_additional_parameters(k->rsa);
			break;
		default:
			buffer_clear(&e->request);
			goto send;
		}
		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)) {
		case SSH_AGENT_CONSTRAIN_LIFETIME:
			death = time(NULL) + buffer_get_int(&e->request);
			break;
		case SSH_AGENT_CONSTRAIN_CONFIRM:
			confirm = 1;
			break;
		default:
			break;
		}
	}
	if (lifetime && !death)
		death = time(NULL) + lifetime;
#ifdef __CYGWIN__
	if (lookup_identity(k, version, e) == NULL) {
#else
	if (lookup_identity(k, version) == NULL) {
#endif
		Identity *id = xmalloc(sizeof(Identity));
		id->key = k;
		id->comment = comment;
		id->death = death;
		id->confirm = confirm;
#ifdef __CYGWIN__
    /* set the authorized user for this key, i.e, the one adding the key */
    id->euid = e->euid;
    id->egid = e->egid;
#endif
		TAILQ_INSERT_TAIL(&tab->idlist, id, next);
		/* Increment the number of identities. */
		tab->nentries++;
	} else {
		key_free(k);
		xfree(comment);
	}
send:
	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output,
	    success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
}

/* XXX todo: encrypt sensitive data with passphrase */
static void
process_lock_agent(SocketEntry *e, int lock)
{
	int success = 0;
	char *passwd;

	passwd = buffer_get_string(&e->request, NULL);
	if (locked && !lock && strcmp(passwd, lock_passwd) == 0) {
		locked = 0;
		memset(lock_passwd, 0, strlen(lock_passwd));
		xfree(lock_passwd);
		lock_passwd = NULL;
		success = 1;
	} else if (!locked && lock) {
		locked = 1;
		lock_passwd = xstrdup(passwd);
		success = 1;
	}
	memset(passwd, 0, strlen(passwd));
	xfree(passwd);

	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output,
	    success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
}

static void
no_identities(SocketEntry *e, u_int type)
{
	Buffer msg;

	buffer_init(&msg);
	buffer_put_char(&msg,
	    (type == SSH_AGENTC_REQUEST_RSA_IDENTITIES) ?
	    SSH_AGENT_RSA_IDENTITIES_ANSWER : SSH2_AGENT_IDENTITIES_ANSWER);
	buffer_put_int(&msg, 0);
	buffer_put_int(&e->output, buffer_len(&msg));
	buffer_append(&e->output, buffer_ptr(&msg), buffer_len(&msg));
	buffer_free(&msg);
}

#ifdef SMARTCARD
static void
process_add_smartcard_key (SocketEntry *e)
{
	char *sc_reader_id = NULL, *pin;
	int i, 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 (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:
			break;
		}
	}
	if (lifetime && !death)
		death = time(NULL) + lifetime;

	keys = sc_get_keys(sc_reader_id, pin);
	xfree(sc_reader_id);
	xfree(pin);

	if (keys == NULL || keys[0] == NULL) {
		error("sc_get_keys failed");
		goto send;
	}
	for (i = 0; keys[i] != NULL; i++) {
		k = keys[i];
		version = k->type == KEY_RSA1 ? 1 : 2;
		tab = idtab_lookup(version);
#ifdef __CYGWIN__
		if (lookup_identity(k, version, e) == NULL) {
#else
		if (lookup_identity(k, version) == NULL) {
#endif
			id = xmalloc(sizeof(Identity));
			id->key = k;
			id->comment = sc_get_key_label(k);
			id->death = death;
			id->confirm = confirm;
			TAILQ_INSERT_TAIL(&tab->idlist, id, next);
			tab->nentries++;
			success = 1;
		} else {
			key_free(k);
		}
		keys[i] = NULL;
	}
	xfree(keys);
send:
	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output,
	    success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
}

static void
process_remove_smartcard_key(SocketEntry *e)
{
	char *sc_reader_id = NULL, *pin;
	int i, version, success = 0;
	Key **keys, *k = NULL;
	Identity *id;
	Idtab *tab;

	sc_reader_id = buffer_get_string(&e->request, NULL);
	pin = buffer_get_string(&e->request, NULL);
	keys = sc_get_keys(sc_reader_id, pin);
	xfree(sc_reader_id);
	xfree(pin);

	if (keys == NULL || keys[0] == NULL) {
		error("sc_get_keys failed");
		goto send;
	}
	for (i = 0; keys[i] != NULL; i++) {
		k = keys[i];
		version = k->type == KEY_RSA1 ? 1 : 2;
#ifdef __CYGWIN__
		if ((id = lookup_identity(k, version, e)) != NULL) {
#else
		if ((id = lookup_identity(k, version)) != NULL) {
#endif
			tab = idtab_lookup(version);
			TAILQ_REMOVE(&tab->idlist, id, next);
			tab->nentries--;
			free_identity(id);
			success = 1;
		}
		key_free(k);
		keys[i] = NULL;
	}
	xfree(keys);
send:
	buffer_put_int(&e->output, 1);
	buffer_put_char(&e->output,
	    success ? SSH_AGENT_SUCCESS : SSH_AGENT_FAILURE);
}
#endif /* SMARTCARD */

/* dispatch incoming messages */

static void
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_u32(cp);
	if (msg_len > 256 * 1024) {
		close_socket(e);
		return;
	}
	if (buffer_len(&e->input) < msg_len + 4)
		return;

	/* move the current input to e->request */
	buffer_consume(&e->input, 4);
	buffer_clear(&e->request);
	buffer_append(&e->request, buffer_ptr(&e->input), msg_len);
	buffer_consume(&e->input, msg_len);
	type = buffer_get_char(&e->request);

	/* check wheter agent is locked */
	if (locked && type != SSH_AGENTC_UNLOCK) {
		buffer_clear(&e->request);
		switch (type) {
		case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
		case SSH2_AGENTC_REQUEST_IDENTITIES:
			/* send empty lists */
			no_identities(e, type);
			break;
		default:
			/* send a fail message for all other request types */
			buffer_put_int(&e->output, 1);
			buffer_put_char(&e->output, SSH_AGENT_FAILURE);
		}
		return;
	}

	debug("type %d", type);
	switch (type) {
	case SSH_AGENTC_LOCK:
	case SSH_AGENTC_UNLOCK:
		process_lock_agent(e, type == SSH_AGENTC_LOCK);
		break;
	/* ssh1 */
	case SSH_AGENTC_RSA_CHALLENGE:
		process_authentication_challenge1(e);
		break;
	case SSH_AGENTC_REQUEST_RSA_IDENTITIES:
		process_request_identities(e, 1);
		break;
	case SSH_AGENTC_ADD_RSA_IDENTITY:
	case SSH_AGENTC_ADD_RSA_ID_CONSTRAINED:
		process_add_identity(e, 1);
		break;
	case SSH_AGENTC_REMOVE_RSA_IDENTITY:
		process_remove_identity(e, 1);
		break;
	case SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES:
		process_remove_all_identities(e, 1);
		break;
	/* ssh2 */
	case SSH2_AGENTC_SIGN_REQUEST:
		process_sign_request2(e);
		break;
	case SSH2_AGENTC_REQUEST_IDENTITIES:
		process_request_identities(e, 2);
		break;
	case SSH2_AGENTC_ADD_IDENTITY:
	case SSH2_AGENTC_ADD_ID_CONSTRAINED:
		process_add_identity(e, 2);
		break;
	case SSH2_AGENTC_REMOVE_IDENTITY:
		process_remove_identity(e, 2);
		break;
	case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
		process_remove_all_identities(e, 2);
		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:
		process_remove_smartcard_key(e);
		break;
#endif /* SMARTCARD */
	default:
		/* Unknown message.  Respond with failure. */
		error("Unknown message %d", type);
		buffer_clear(&e->request);
		buffer_put_int(&e->output, 1);
		buffer_put_char(&e->output, SSH_AGENT_FAILURE);
		break;
	}
}

static void
#ifdef __CYGWIN__
new_socket(sock_type type, int fd, uid_t euid, gid_t egid)
#else
new_socket(sock_type type, int fd)
#endif
{
	u_int i, old_alloc, new_alloc;

	set_nonblock(fd);

	if (fd > max_fd)
		max_fd = fd;

	for (i = 0; i < sockets_alloc; i++)
		if (sockets[i].type == AUTH_UNUSED) {
			sockets[i].fd = fd;
			buffer_init(&sockets[i].input);
			buffer_init(&sockets[i].output);
			buffer_init(&sockets[i].request);
			sockets[i].type = type;
#ifdef __CYGWIN__
      if (r_flag) {
        sockets[i].euid = euid;
        sockets[i].egid = egid;
      }
#endif
			return;
		}
	old_alloc = sockets_alloc;
	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_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;
#ifdef __CYGWIN__
  if (r_flag) {
    sockets[old_alloc].euid = euid;
    sockets[old_alloc].egid = egid;
  }
#endif
}

static int
prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp)
{
	u_int i, sz;
	int n = 0;

	for (i = 0; i < sockets_alloc; i++) {
		switch (sockets[i].type) {
		case AUTH_SOCKET:
		case AUTH_CONNECTION:
			n = MAX(n, sockets[i].fd);
			break;
		case AUTH_UNUSED:
			break;
		default:
			fatal("Unknown socket type %d", sockets[i].type);
			break;
		}
	}

	sz = howmany(n+1, NFDBITS) * sizeof(fd_mask);
	if (*fdrp == NULL || sz > *nallocp) {
		if (*fdrp)
			xfree(*fdrp);
		if (*fdwp)
			xfree(*fdwp);
		*fdrp = xmalloc(sz);
		*fdwp = xmalloc(sz);
		*nallocp = sz;
	}
	if (n < *fdl)
		debug("XXX shrink: %d < %d", n, *fdl);
	*fdl = n;
	memset(*fdrp, 0, sz);
	memset(*fdwp, 0, sz);

	for (i = 0; i < sockets_alloc; i++) {
		switch (sockets[i].type) {
		case AUTH_SOCKET:
		case AUTH_CONNECTION:
			FD_SET(sockets[i].fd, *fdrp);
			if (buffer_len(&sockets[i].output) > 0)
				FD_SET(sockets[i].fd, *fdwp);
			break;
		default:
			break;
		}
	}
	return (1);
}

static void
after_select(fd_set *readset, fd_set *writeset)
{
	struct sockaddr_un sunaddr;
	socklen_t slen;
	char buf[1024];
	int len, sock;
	u_int i;
	uid_t euid;
	gid_t egid;

	for (i = 0; i < sockets_alloc; i++)
		switch (sockets[i].type) {
		case AUTH_UNUSED:
			break;
		case AUTH_SOCKET:
			if (FD_ISSET(sockets[i].fd, readset)) {
				slen = sizeof(sunaddr);
				sock = accept(sockets[i].fd,
				    (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;
				}
#ifdef __CYGWIN__
        if (!r_flag) {
#endif
				  if ((euid != 0) && (getuid() != euid)) {
					  error("uid mismatch: "
					      "peer euid %u != uid %u",
					      (u_int) euid, (u_int) getuid());
					  close(sock);
					  break;
				  }
#ifdef __CYGWIN__
        }
				new_socket(AUTH_CONNECTION, sock, euid, egid);
#else
				new_socket(AUTH_CONNECTION, sock);
#endif
			}
			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);
				if (len <= 0) {
					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);
				if (len <= 0) {
					close_socket(&sockets[i]);
					break;
				}
				buffer_append(&sockets[i].input, buf, len);
				process_message(&sockets[i]);
			}
			break;
		default:
			fatal("Unknown type %d", sockets[i].type);
		}
}

static void
cleanup_socket(void)
{
	if (socket_name[0])
		unlink(socket_name);
	if (socket_dir[0])
		rmdir(socket_dir);
}

void
cleanup_exit(int i)
{
	cleanup_socket();
	_exit(i);
}

/*ARGSUSED*/
static void
cleanup_handler(int sig)
{
	cleanup_socket();
	_exit(2);
}

/*ARGSUSED*/
static void
check_parent_exists(int sig)
{
	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 */
	}
	mysignal(SIGALRM, check_parent_exists);
	alarm(10);
	errno = save_errno;
}

static void
usage(void)
{
	logit("Usage: %s [options] [command [args ...]]\n", __progname);
	logit("Options:\n");
	logit("  -c          Generate C-shell commands on stdout.\n");
	logit("  -s          Generate Bourne shell commands on stdout.\n");
	logit("  -k          Kill the current agent.\n");
	logit("  -d          Debug mode.\n");
	logit("  -a socket   Bind agent socket to given name.\n");
	logit("  -t life     Default identity lifetime (seconds).\n");
#ifdef __CYGWIN__
  logit("  -r          Don't fork (for use with cygrunsrv).\n");
#endif
	exit(1);
}

int
main(int ac, char **av)
{
	int c_flag = 0, d_flag = 0, k_flag = 0, s_flag = 0;
	int sock, fd, ch;
	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
	int prev_mask;
	extern int optind;
	extern char *optarg;
	pid_t pid;
	char pidstrbuf[1 + 3 * sizeof pid];

	/* 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 = ssh_get_progname(av[0]);

  /* start with logging to stderr */
  log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 1);

	init_rng();
	seed_rng();

#ifdef __CYGWIN__
	while ((ch = getopt(ac, av, "cdksra:t:")) != -1) {
#else
	while ((ch = getopt(ac, av, "cdksa:t:")) != -1) {
#endif
		switch (ch) {
		case 'c':
			if (s_flag)
				usage();
			c_flag++;
			break;
		case 'k':
			k_flag++;
			break;
		case 's':
			if (c_flag)
				usage();
			s_flag++;
			break;
		case 'd':
			if (d_flag)
				usage();
			d_flag++;
			break;
		case 'a':
			agentsocket = optarg;
			break;
		case 't':
			if ((lifetime = convtime(optarg)) == -1) {
				logit("Invalid lifetime\n");
				usage();
			}
			break;
#ifdef __CYGWIN__
		case 'r':
      if (r_flag)
        usage();
      r_flag++;
      log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0);
      logit("started in non-daemon mode");
      break;
#endif
		default:
			usage();
		}
	}
	ac -= optind;
	av += optind;

	if (ac > 0 && (c_flag || k_flag || s_flag || d_flag
#ifdef __CYGWIN__
        || r_flag
#endif
        ))
		usage();

	if (ac == 0 && !c_flag && !s_flag) {
		shell = getenv("SHELL");
		if (shell != NULL &&
		    strncmp(shell + strlen(shell) - 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 = (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) {
			perror("kill");
			exit(1);
		}
		format = c_flag ? "unsetenv %s;\n" : "unset %s;\n";
		printf(format, SSH_AUTHSOCKET_ENV_NAME);
		printf(format, SSH_AGENTPID_ENV_NAME);
		printf("echo Agent pid %ld killed;\n", (long)pid);
		exit(0);
	}
	parent_pid = getpid();

	if (agentsocket == NULL) {
		/* Create private directory for agent socket */
	 	strlcpy(socket_dir, "/tmp/ssh-XXXXXXXXXX", sizeof socket_dir);
	 	if (mkdtemp(socket_dir) == NULL) {
      logit("mkdtemp: private socket dir: %s", strerror(errno));
	 		exit(1);
	 	}
#ifdef __CYGWIN__
    chmod(socket_dir, 0744);
#endif
	 	snprintf(socket_name, sizeof socket_name, "%s/agent.%ld", socket_dir,
	 	    (long)parent_pid);
	} else {
	  /* Try to use specified agent socket */
	  socket_dir[0] = '\0';
	  strlcpy(socket_name, agentsocket, sizeof socket_name);
	}

	/*
	 * Create socket early so it will exist before command gets run from
	 * the parent.
	 */
	sock = socket(AF_UNIX, SOCK_STREAM, 0);

	if (sock < 0) {
    logit("socket: %s", strerror(errno));
		*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));
	prev_mask = umask(0177);
	if (bind(sock, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) {
	  logit("bind: %s", strerror(errno));
		*socket_name = '\0'; /* Don't unlink any existing file */
		umask(prev_mask);
		cleanup_exit(1);
	}
	umask(prev_mask);
#ifdef __CYGWIN__
  chmod(socket_name, 0644);

  /* register the socket name in the environment */
  if (r_flag) {
    if (ssh_agent_reg(socket_name)) {
      error("registering socket name failed");
    }
  }
#endif
	if (listen(sock, SSH_LISTEN_BACKLOG) < 0) {
    logit("listen: %s", strerror(errno));
		cleanup_exit(1);
	}

	/*
	 * Fork, and have the parent execute the command, if any, or present
	 * the socket data.  The child continues as the authentication agent.
	 */
	if (d_flag) {
		log_init(__progname, SYSLOG_LEVEL_DEBUG1, SYSLOG_FACILITY_AUTH, 1);
		format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n";
		printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
		    SSH_AUTHSOCKET_ENV_NAME);
		printf("echo Agent pid %ld;\n", (long)parent_pid);
		goto skip;
	}
#ifdef __CYGWIN__
  if (r_flag) {
    goto skip;
  }
#endif
	pid = fork();
	if (pid == -1) {
		logit("fork: %s", strerror(errno));
		cleanup_exit(1);
	}
	if (pid != 0) {		/* Parent - execute the given command. */
		close(sock);
		snprintf(pidstrbuf, sizeof pidstrbuf, "%ld", (long)pid);
		if (ac == 0) {
			format = c_flag ? "setenv %s %s;\n" : "%s=%s; export %s;\n";
			printf(format, SSH_AUTHSOCKET_ENV_NAME, socket_name,
			    SSH_AUTHSOCKET_ENV_NAME);
			printf(format, SSH_AGENTPID_ENV_NAME, pidstrbuf,
			    SSH_AGENTPID_ENV_NAME);
			printf("echo Agent pid %ld;\n", (long)pid);
			exit(0);
		}
		if (setenv(SSH_AUTHSOCKET_ENV_NAME, socket_name, 1) == -1 ||
		    setenv(SSH_AGENTPID_ENV_NAME, pidstrbuf, 1) == -1) {
			logit("setenv: %s", strerror(errno));
			exit(1);
		}
		execvp(av[0], av);
		logit("%s: %s", av[0], strerror(errno));
		exit(1);
	}
	/* child */
	log_init(__progname, SYSLOG_LEVEL_INFO, SYSLOG_FACILITY_AUTH, 0);

	if (setsid() == -1) {
		error("setsid: %s", strerror(errno));
		cleanup_exit(1);
	}

	(void)chdir("/");
	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 */
	rlim.rlim_cur = rlim.rlim_max = 0;
	if (setrlimit(RLIMIT_CORE, &rlim) < 0) {
		error("setrlimit RLIMIT_CORE: %s", strerror(errno));
		cleanup_exit(1);
	}
#endif

skip:
	new_socket(AUTH_SOCKET, sock, 0, 0);
	if (ac > 0) {
		mysignal(SIGALRM, check_parent_exists);
		alarm(10);
	}
	idtab_init();
	if (!d_flag && !r_flag)
		signal(SIGINT, SIG_IGN);
	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, cleanup_handler);
	signal(SIGTERM, cleanup_handler);
	nalloc = 0;

	while (1) {
		prepare_select(&readsetp, &writesetp, &max_fd, &nalloc);
		if (select(max_fd + 1, readsetp, writesetp, NULL, NULL) < 0) {
			if (errno == EINTR)
				continue;
			fatal("select: %s", strerror(errno));
		}
		after_select(readsetp, writesetp);
	}
	/* NOTREACHED */
}



--------------040200080800090601020602
Content-Type: text/plain; charset=us-ascii

--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/
--------------040200080800090601020602--

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019