/* Copyright (C) 1989,1990 Transarc Corporation - All rights reserved */
/*
 * (C) COPYRIGHT IBM CORPORATION 1988, 1989
 * LICENSED MATERIALS - PROPERTY OF IBM
 */

/*
 * Revision 2.1  1990/08/07  19:12:42
 * Start with clean version to sync test and dev trees.
 *
 * Revision 1.15  90/02/07  09:51:17
 * Don't depend on GetPassword working, since it no longer is enabled by default.
 * 
 * Revision 1.14  89/12/13  09:53:30
 * Some problems that didn't appear on the RT, which is apparently the only
 *   place this had actually been compiled before(!).
 * Also call ka_CellConfig so that cell names (used by StringToKey) are
 *   recognized early on.
 * Request a tgt with lifetime of 100+ hours, since the default is exactly
 *   100 hosts, the ticket's end time should be trucated to this limit.
 *   This the result determines where we have a new or old server
 *   interface.
 * 
 * Revision 1.13  89/06/06  10:44:56
 * Actually exit when testing old tickets.
 * 
 * Revision 1.12  89/05/24  16:29:11
 * Major changes to use the fast keys mode to exercise oldKeys and key cache
 * 
 * Revision 1.11  89/03/10  10:25:25
 * Added #include for "kauth.h" instead of relying on implicit reference
 *   to <kauth.h>.
 * 
 * Revision 1.10  89/02/17  12:22:24
 * add hostname adaptability.
 * Call setpag and exec shell file of choice.
 * 
 * Revision 1.9  89/02/14  16:07:36 
 * Added several tests to detect incomatible changes to StringToKey.
 * Call SetPassword with various illegal keys.
 * Call des_check_key_parity and des_is_weak_key.
 * Call ka_Init with improved interface.
 * 
 * Revision 1.8  89/02/09  10:17:00 
 * Flush reference to obsolete kaclient.h.
 * 
 * Revision 1.7  89/02/02  14:52:26 
 * Updated copyright.
 * Call GetPassword at lookback addr to get password.
 * Use ticket w/ no expiration time.
 * Require constant key for AuthServer.Admin ("authserv").
 * Test lcstring, ucstring, and strcasecmp.
 * 
 * Revision 1.6  89/01/30  10:21:47
 * Minor cosmetic changes.
 * 
 * Revision 1.5  89/01/30  09:56:24
 * Added copyright notice.
 *  */

#include <sys/types.h>
#include <des.h>
#include <afs/auth.h>
#include <afs/cellconfig.h>
#include <rx/xdr.h>
#include <rx/rx.h>
#include <rx/rxkad.h>
#include <lock.h>
#include <ubik.h>
#include <lwp.h>
#include "kauth.h"
#include "kautils.h"

char *whoami;
char *localCell;
char  name[MAXKTCNAMELEN] = "guest";
char  inst[MAXKTCNAMELEN] = "";

char  aname[] = "AuthServer";
char  ainst[] = "Admin";

static print_entry (tentry, name, instance)
  struct kaentryinfo *tentry;
  char *name;
  char *instance;
{   Date now = time(0);
    char bob[KA_TIMESTR_LEN];

    if (tentry->minor_version != KAMINORVERSION)
	printf ("Minor version number mismatch: got %d, expected %d\n",
		tentry->minor_version, KAMINORVERSION);
    ka_PrintUserID ("User data for ", name, instance, "");
    {	char *prefix = " (";
#define NEWPREFIX "+"
	if (tentry->flags & KAFADMIN) { printf ("%sADMIN", prefix); prefix = NEWPREFIX; }
	if (tentry->flags & KAFNOTGS) { printf ("%sNOTGS", prefix); prefix = NEWPREFIX; }
	if (tentry->flags & KAFNOCPW) { printf ("%sNOCPW", prefix); prefix = NEWPREFIX; }
	if (tentry->flags & KAFNOSEAL) { printf ("%sNOSEAL", prefix); prefix = NEWPREFIX; }
	if (tentry->user_expiration <= now) { printf ("%sexpired", prefix); prefix = NEWPREFIX; }
	if (strcmp (prefix, NEWPREFIX) == 0) printf (")\n");
	else printf ("\n");
    }
    printf ("  key (%d):", tentry->key_version);
    ka_PrintBytes (&tentry->key, sizeof(tentry->key));
    ka_timestr(tentry->change_password_time, bob, KA_TIMESTR_LEN);
    printf (", last cpw: %s\n", bob);
    ka_timestr(tentry->user_expiration,bob, KA_TIMESTR_LEN);
    printf ("  entry expires on %s.  Max ticket lifetime %.2f hours.\n",
	    bob, tentry->max_ticket_lifetime / 3600.0);
    ka_timestr(tentry->modification_time,bob, KA_TIMESTR_LEN);
    printf ("  last mod on %s by ", bob);
    ka_PrintUserID ("", tentry->modification_user.name,
		    tentry->modification_user.instance, "\n");
}

int sign (x)
  int x;
{   if (x<0) return -1;
    if (x>0) return 1;
    return 0;
}

void TestOldKeys (userkey)
  struct ktc_encryptionKey *userkey;
{

#define MAXADMINTOKENS 30
#define MAXTGSTOKENS 10
    int nAdminTokens, nTGSTokens;
    struct ktc_token adminTokens[MAXADMINTOKENS];
    struct ktc_token tgsTokens[MAXTGSTOKENS];

    /* depends on 10 updates in 10 seconds to get AutoCPW to trigger,
       also 90 seconds is the maximum password age,
       also 11 old keys per block. */

    static int OKTestVector[] = {
	0, 1000, 3000, 1005, 1005, 1005, 1006, 1006, 1007, 1007, 1007, 1007, 1008,
	1011, 3012, 3012, 1015, 3017, 3017, 3017,
	1021, 2024, 4024, 13026, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 1028, 
	1031, 1031, 13032, 13032, 13032, 13032, 13032, 13032, 2037, 4037,
	13040, 13040, 13041, 13041, 13042, 13042, 2049, 4049,
	13050, 13051, 13052, 54, 55, 56,
	2061, 4061, 65, 65, 65, 65, 65, 65, 65, 65, 65,
	2073, 4073, 13073, 13073, 13074, 13075, 77,
	2085, 4085, 85, 85, 85, 85, 85, 85, 85,
	13093,
	2100, 1103 /* should change primary on aa */, 4105,
	13111};
    int nOKTestVector = sizeof(OKTestVector) / sizeof(int);

    long  code;
    struct ktc_token tgt;
    struct ktc_token atoken;
    struct ubik_client *aconn;		/* connection to authentication service */
    struct ubik_client *tgsConn;	/* connection to TGS */
    struct ubik_client *adminConn;	/* connection to maintenance service */
    struct ktc_token ttoken;
    struct kaentryinfo aentry;
    struct ka_debugInfo info;
    Date  now = time(0);
    Date  start;
    int	  notify;
    int   i;
    struct ktc_encryptionKey key;
    char *aaname = "krbtgt";
    char *aainst = "FAKECELL.EDU";

    printf ("checking old keys\n");

    /* get tickets */
    if ((code = ka_AuthServerConn (localCell, KA_AUTHENTICATION_SERVICE, 0, &aconn)) ||
	(code = ka_Authenticate (name, inst, localCell, aconn,
				 KA_TICKET_GRANTING_SERVICE,
				 userkey, now, now+3600, &tgt, 0 )) ||
	(code = ka_Authenticate (name, inst, localCell, aconn,
				 KA_MAINTENANCE_SERVICE,
				 userkey, now, now+3600, &atoken, 0 )) ||
	(code = ka_AuthServerConn (localCell, KA_TICKET_GRANTING_SERVICE, 0, &tgsConn)) ||
	(code = ka_AuthServerConn (localCell, KA_MAINTENANCE_SERVICE, &atoken, &adminConn))
	){
      abort:
	com_err (whoami, code, "testing old keys");
	exit (1);
    }

    code = ubik_Call (KAM_GetRandomKey, adminConn, 0, &key);
    if (code) goto abort;
    code = ubik_Call (KAM_CreateUser, adminConn, 0, aaname, aainst, key);
    if (code == KAEXIST) printf ("Alternate Admin User already exists\n");
    else if (code) goto abort;

    nAdminTokens = nTGSTokens = 0;
    start = time(0);
    notify = 10;
    for (i=0; i<nOKTestVector; i++) {
	int v = OKTestVector[i];	/* get test vector */
	int sleep;

	sleep = v % 1000;
	v = v / 1000;

	if ((now=time(0)) < start+sleep)
	    IOMGR_Sleep (start+sleep - now);
	else IOMGR_Poll();
	now = time(0);
	if (sleep >= notify) {
	    printf ("Now at %d seconds\n", sleep);
	    notify = sleep + 10;
	}

	switch (v) {
	  case 1:	/* set krbtgt.FAKECELL.EDU password */
	    code = ubik_Call (KAM_GetRandomKey, adminConn, 0, &key);
	    if (code) goto abort_1;
	    code = ubik_Call (KAM_SetPassword, adminConn, 0, aaname, aainst, 0, key);
	    break;
	  case 3:	/* set AuthServer.Admin password */
	  case 13:	/* and remember admin ticket */
	    code = ubik_Call (KAM_GetRandomKey, adminConn, 0, &key);
	    if (code) goto abort_1;
	    code = ubik_Call (KAM_SetPassword, adminConn, 0, aname, ainst, 0, key);
	    if (v == 3) break;
	  case 4:	/* remeber Admin ticket and TGS ticket */
	    if (nAdminTokens >= MAXADMINTOKENS) printf ("Too many admin tokens\n");
	    else code = ka_Authenticate (name, inst, localCell, aconn, KA_MAINTENANCE_SERVICE,
					 userkey, now, now+3600,
					 &adminTokens[nAdminTokens++], 0);
	    if (code) goto abort_1;
	    if (v != 4) break;
	    if (nTGSTokens >= MAXTGSTOKENS) printf ("Too many tgs tokens\n");
	    else code = ka_Authenticate (name, inst, localCell, aconn, KA_TICKET_GRANTING_SERVICE,
					 userkey, now, now+3600,
					 &tgsTokens[nTGSTokens++], 0);
	    break;
	  case 2:
	    code = ubik_Call (KAM_Debug, adminConn, 0, KAMAJORVERSION, 0, &info);
	    if (code) goto abort_1;
	    now = time(0);
	    printf ("Now at %d seconds (really %d): %d updates and %d seconds remaining\n",
		    sleep, (now-start), info.updatesRemaining, info.nextAutoCPW-now);
	    if (info.updatesRemaining > 1)
		printf ("Too many updates needed at time %d\n", sleep);
	    while ((now=time(0))<info.nextAutoCPW) {
		printf ("...waiting for next auto CPW\n");
		if (info.nextAutoCPW-now > 1) IOMGR_Sleep (1);
		else IOMGR_Poll();
	    }
	    code = ubik_Call (KAM_SetFields, adminConn, 0,
			      name, inst, 0, 0, 100*3600, 0, /* spares */ 0,0);
	    break;
	  case 0:
	    code = ubik_Call (KAM_GetEntry, adminConn, 0, aname, ainst, KAMAJORVERSION, &aentry);
	    break;
	}
	if (code) {
	  abort_1:
	    com_err (whoami, code, "at %d seconds: calling server with v=%x", sleep, v);
	    exit (2);
	}
    }

    printf ("Trying %d Admin tokens\n", nAdminTokens);
    for (i=0; i<nAdminTokens; i++) {
	int j;
	struct ubik_client *conn;

	for (j=i+1; j<nAdminTokens; j++)
	    if (adminTokens[i].kvno == adminTokens[j].kvno)
		printf ("Two admin tokens with kvno %d: %d and %d\n",
			(int)adminTokens[i].kvno, i, j);

	code = ka_AuthServerConn (localCell, KA_MAINTENANCE_SERVICE, &adminTokens[i], &conn);
	if (code) {
	  abort_ta:
	    com_err (whoami, code, "Checking admin token #%d with kvno %d\n",
		     i, (int)adminTokens[i].kvno);
	    exit (5);
	}
	code = ubik_Call (KAM_GetEntry, conn, 0, aname, ainst, KAMAJORVERSION, &aentry);
	if (code) goto abort_ta;
    }

    printf ("Trying %d TGS tokens\n", nTGSTokens);
    for (i=0; i<nTGSTokens; i++) {
	int j;
	struct ktc_token token;

	for (j=i+1; j<nTGSTokens; j++)
	    if (tgsTokens[i].kvno == tgsTokens[j].kvno)
		printf ("Two tgs tokens with kvno %d: %d and %d\n",
			(int)tgsTokens[i].kvno, i, j);

	code = ka_GetToken (name, inst, localCell, name, inst, tgsConn, now, now+3600, &tgsTokens[i], "", &token);
	if (code) {
	    com_err (whoami, code, "Checking tgs token #%d with kvno %d\n",
		     i, (int)tgsTokens[i].kvno);
	    exit (6);
	}
    }

    code = ubik_Call (KAM_DeleteUser, adminConn, 0, aaname, aainst);
    if (code) {
	com_err (whoami, code, "Deleting alternate admin user");
	exit (3);
    }
    return;
}

int main (argc, argv)
  int   argc;
  char *argv[];
{
    int   i,j;
    long  serverList[MAXSERVERS];
    int   code;
    char *args[3];
    struct ktc_encryptionKey key;
    struct ktc_token tgt;
    struct ktc_token token;
    struct ktc_token atoken;
    struct ubik_client *aconn;		/* connection to authentication service */
    struct ubik_client *conn;
    struct ubik_client *lpbkConn=0;
    struct kaentryinfo tentry;
    Date  now = time(0);
    Date  end;
    char  password[BUFSIZ];
#if (BUFSIZ<=1000)
       password needs to be at least 1000 chars long;
#endif
    static char source[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0987654321";
    static int  truncate[] = {1000, 750, 562, 432, 316, 237, 178, 133, 100, 75, 56, 42, 32, 24, 18, 13, 10, 8, 6, 4, 3, 2, 1, 0};
    static char answer[sizeof(truncate)/sizeof(int)][32] = {"\250y\3161\315\250\361\274", "\212\133\133\345\236\206\362\200", "\230b\354\334\302F\352\040", "\001\221T\016\265T\376\100", "X\002\224g\250\221\214\340", "\241p\304\032\135\0328\361", "\010\352\037\236sp\046\212", "\3522\352\325\247\313\023W", "\023\310\034\315\265\227L\203", "z\373RJ\233\304\046m", "\310\244\032\375\3704\045\323", "n\224\3022\034\376\013\247", "\217\343\302\364\177\277\052\214", "\357\337\010\054sv\332\057", "\373\373\316u\247\302\362g", "\214O\007m\320\221\301\174", "\002\057OQ\031p\370u", "k\224s\235\362\247\230\206", "\364\233\334\211\235\217k\205", "\352\203m\212s\337\211d", "k\354\256\364\222\352\265\323", "\205\135d\351\326\334\260\313", "\345\250\331\340\265hd\222", "\346\265\352\136\316\205\217\313"};
    struct ktc_encryptionKey correct_key;
    char hostname[64];

    whoami = argv[0];
    args[0] = "";
    args[1] = "-servers";

    /* get this host */
    gethostname(hostname,sizeof(hostname));
    args[2] = hostname;

    /* test string hacking */
    if ((strcmp (lcstring (name, "AbcDe", 5), "abcd") != 0) ||
	(strcmp (lcstring (name, "AbcDe", 6), "abcde") != 0) ||
	(strcmp (ucstring (inst, "AbcDe", 4), "ABC") != 0) ||
	(strcmp (ucstring (inst, "AbcDe", sizeof(inst)), "ABCDE") != 0)) {
	printf ("uc/lc string problem\n");
	exit (1);
    }
#define tryscc(A,B,a,b) (sign(strcasecmp (A,B)) != sign (strcmp (a,b)))
    if (tryscc ("Abc", "abc", "abc", "abc") ||
	tryscc ("aB", "ABC", "ab", "abc") ||
	tryscc ("ABC", "ab", "abc", "ab") ||
	tryscc ("Abcd", "aBg", "abcd", "abg")) {
	printf ("strcasecmp problem\n");
	exit (1);
    }

    strcpy (password, "");
    for (i=0; i<100; i++)
	for (j=0; j<10; j++)
	    password[i*10+j] = source[i % (sizeof(source)-1)];

    code = ka_CellConfig (AFSCONF_CLIENTNAME);
    if (code) com_err (whoami, code, "calling cell config");
    localCell = ka_LocalCell();

    for (i=0; i<(sizeof(truncate)/sizeof(int)); i++) {
	password[truncate[i]] = 0;
	ka_StringToKey (password, "andy.edu", &key);
	ka_ReadBytes (answer[i], &correct_key, sizeof(key));
	if (bcmp (&key, &correct_key, sizeof(key)) != 0) {
	    printf ("String to key error converting '%s'; should be '%s' instead got '",
		    password, answer[i]);
	    ka_PrintBytes (&key, sizeof(key));
	    printf ("'\n");
	    exit (1);
	}
    }
    bzero (password, sizeof(password));
    j=0;				/* current password length */
    for (i=(sizeof(truncate)/sizeof(int))-1; i>=0; i--) {
	while (j<truncate[i]) {
	    password[j] = source[j/10 % (sizeof(source)-1)];
	    j++;
	}
	ka_StringToKey (password, "andy.edu", &key);
	ka_ReadBytes (answer[i], &correct_key, sizeof(key));
	if (bcmp (&key, &correct_key, sizeof(key)) != 0) {
	    printf ("String to key error converting '%s'; should be '%s' instead got '",
		    password, answer[i]);
	    ka_PrintBytes (&key, sizeof(key));
	    printf ("'\n");
	}
    }

    strcpy (name, "guest");
    strcpy (inst, "");

    code = rx_Init(0);
    if (code) {
	com_err (whoami, code, "rx_Init'ing");
	exit (1);
    }
    if (code = ka_Init(0)) {
	com_err (whoami, code, "ka_Init'ing");
	exit (1);
    }
    if (code = ubik_ParseClientList(3, args, serverList)) {
	com_err (whoami, code, "parsing Ubik server list");
	exit (1);
    }
    ka_ExplicitCell (localCell, serverList);

    {   struct rx_connection    *conns[2];
	struct rx_securityClass *sc;
	int			 si;	/* security class index */

	sc = (struct rx_securityClass *) rxnull_NewClientSecurityObject();
	si = RX_SCINDEX_NULL;
	conns[0] = rx_NewConnection (htonl(INADDR_LOOPBACK), htons(AFSCONF_KAUTHPORT),
				     KA_MAINTENANCE_SERVICE, sc, si);
	conns[1] = 0;
	code = ubik_ClientInit(conns, &lpbkConn);
	if (code) {
abort_4:
	    com_err (whoami, code,
		     "getting %s's password via loopback connection to GetPassword", name);
	    exit (1);
	}
	code = ubik_Call (KAM_GetPassword, lpbkConn, 0, name, &key);
	if (code == KANOAUTH) {
	    printf ("GetPassword disabled\n");
	    ka_StringToKey (name, localCell, &key);
	}
	else if (code) goto abort_4;
    }

    /* first just get TGS ticket */
    code = ka_AuthServerConn (localCell, KA_AUTHENTICATION_SERVICE, 0, &aconn);
    if (code) {
      abort:
	com_err (whoami, code, "connecting to authentication service");
	exit (1);
    }
    end =  now+100*3600+2;
    code = ka_Authenticate (name, inst, localCell, aconn,
			    KA_TICKET_GRANTING_SERVICE,
			    &key, now, end, &tgt, 0);
    if (code) goto abort;
    if (tgt.endTime == end) {
	fprintf (stderr, "*** AuthTicket expires too late: must be old style sever ***\n");
    } else if (tgt.endTime != now+100*3600) {
	fprintf (stderr, "Bogus expiration because lifetime (%d) wrong\n",
		 tgt.endTime - tgt.startTime);
	exit (1);
    }

    /* try to get ticket w/ time jitter */
    code = ka_Authenticate (name, inst, localCell, aconn,
			    KA_MAINTENANCE_SERVICE, &key,
			    now+KTC_TIME_UNCERTAINTY/2, now+3600, &token, 0);
    if (code) {
abort_1:
	com_err (whoami, code, "using admin ticket with time jitter");
	exit (1);
    }

    code = ka_AuthServerConn (localCell, KA_MAINTENANCE_SERVICE, &token, &conn);
    if (code) goto abort_1;
    code = ubik_Call (KAM_GetEntry, conn, 0, name, inst, KAMAJORVERSION, &tentry);
    if (code) goto abort_1;
    print_entry (&tentry, name, inst);
    
    {   struct ktc_encryptionKey badkey;

	bcopy (&key, &badkey, sizeof(badkey));
	*(int *)&badkey ^= 1;		/* toggle some bit */
	code = ubik_Call (KAM_SetPassword, conn, 0, name, inst, 0, badkey);
	if (code != KABADKEY) {
	  abort_5:
	    com_err (whoami, code, "Trying to set bad key");
	    exit(1);
	}
	bzero (&badkey, sizeof(badkey));
	code = ubik_Call (KAM_SetPassword, conn, 0, name, inst, 0, badkey);
	if (code != KABADKEY) goto abort_5;
	code = ubik_Call (KAM_SetPassword, conn, 0, name, inst, 9999, key);
	if (code != KABADARGUMENT) goto abort_5;
    }

    /* try using ticket with no expiration time */
    {   struct ktc_encryptionKey akey;
	struct kaentryinfo aentry;

	ka_StringToKey ("authserv", localCell, &akey);
	
	code = ubik_Call (KAM_SetPassword, conn, 0, aname, ainst, 0, akey);
	if (code) {
	  abort_6:
	    com_err (whoami, code, "Checking SetPassword");
	    exit (2);
	}
	code = ubik_Call (KAM_GetEntry, conn, 0, aname, ainst, KAMAJORVERSION, &aentry);
	if (code) goto abort_6;
	atoken.kvno = aentry.key_version;
	for (i=0; i<sizeof(aentry.key); i++) if (((char *)&aentry.key)[i]) {
	    code = KABADKEY;
	    goto abort_6;
	}
	code = ubik_Call (KAM_GetRandomKey, conn, 0, &atoken.sessionKey);
	if (code) goto abort_3;
	printf ("Got random sessionKey: ");
	ka_PrintBytes (&atoken.sessionKey, sizeof(key));
	printf ("\n");

	atoken.startTime = 0;
	atoken.endTime = NEVERDATE;
	code = tkt_MakeTicket (atoken.ticket, &atoken.ticketLen, &akey, name, inst, "",
			       atoken.startTime, atoken.endTime, &atoken.sessionKey,
			       0, aname, ainst);
	if (code) {
abort_3:
	    com_err (whoami, code, "faking up AuthServer ticket");
	    exit (1);
	}
	{   struct ktc_principal client;
	    struct ktc_encryptionKey sessionkey;
	    Date start, end;
	    long host;
	    
	    code = tkt_DecodeTicket (atoken.ticket, atoken.ticketLen, &akey,
				     client.name, client.instance, client.cell,
				     &sessionkey, &host, &start, &end);
	    if (code) goto abort_3;
	    if (code = tkt_CheckTimes (start, end, time(0)) <= 0) goto abort_3;
	    
	    if (!des_check_key_parity (&sessionkey) ||
		des_is_weak_key (&sessionkey))
		{ code = KABADKEY; goto abort_3;}
	}
    }

    code = ka_AuthServerConn (localCell, KA_MAINTENANCE_SERVICE, &atoken, &conn);
    if (code) goto abort_3;
    {   struct kaentryinfo entry;

	code = ubik_Call (KAM_GetEntry, conn, 0, name, inst, KAMAJORVERSION, &entry);
	if (code) goto abort_3;
	if (bcmp (&tentry, &entry, sizeof(entry)) != 0) {
	    printf ("Entries obtained not the same!\n");
	    print_entry (&entry, name, inst);
	}
    }

    /* try bashing a ticket to make sure it fails to work */
    bzero (atoken.ticket+10, 1);
    code = ka_AuthServerConn (localCell, KA_MAINTENANCE_SERVICE, &atoken, &conn);
    if (code) {
	com_err (whoami, code, "contacting admin server with bashed ticket");
	exit (0);			/* this is supposed to happen */
    }
    code = ubik_Call (KAM_GetEntry, conn, 0, name, inst, KAMAJORVERSION, &tentry);
    if (code != RXKADBADTICKET) {
	com_err(whoami, code, "GetEntry failed to fail even with damaged ticket!!!!\n");
	exit (1);
    }

    TestOldKeys (&key);

    printf ("All clear!\n");
    if (argc == 2) {
	code = setpag();
	if (code) com_err (whoami, code, "calling SetPAG");
	else printf ("Calling SetPAG and exec'ing %s\n", argv[1]);
	execve (argv[1], argv+1, 0);
	perror ("execve returned");
    }
    exit (0);
}
