/*
 * Copyright (c) 1991, 1993 
 * Regents of the University of Michigan.  All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that this notice is preserved and that due credit is given
 * to the University of Michigan at Ann Arbor. The name of the University
 * may not be used to endorse or promote products derived from this
 * software without specific prior written permission. This software
 * is provided ``as is'' without express or implied warranty.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#ifndef __STDC__
#include <memory.h>
#endif
#include <time.h>
#include <lber.h>
#include <ldap.h>
#include "ud.h"

#ifdef DEBUG
extern int debug;
#endif

struct entry Entry;
extern LDAP *ld;

extern void * Malloc();
extern void Free();
extern char * my_ldap_dn2ufn();

/*
 *  When displaying entries, display only these attributes, and in this
 *  order.
 */
static char *person_attr_print_order[] = {
	"cn",
	"mail",
	"telephoneNumber",
	"facsimileTelephoneNumber",
	"pager",
	"postalAddress",
	"title",
	"uid",
	"multiLineDescription",
	"homePhone",
	"homePostalAddress",
	"drink",
	"labeledURL",
	"onVacation",
	"vacationMessage",
	"memberOfGroup",
	"lastModifiedBy",
	"lastModifiedTime",
	NULL
};

static char *group_attr_print_order[] = {
	"cn",
	"facsimileTelephoneNumber",
	"telephoneNumber",
	"postalAddress",
	"multiLineDescription",
	"joinable",
	"associatedDomain",
	"owner",
	"moderator",
	"ErrorsTo",
	"rfc822ErrorsTo",
	"RequestsTo",
	"rfc822RequestsTo",
	"member",
	"mail",
	"labeledURL",
	"lastModifiedBy",
	"lastModifiedTime",
	NULL
};

parse_answer(s)
LDAPMessage *s;
{
	int idx;
	char **rdns;
	BerElement *cookie;
	register LDAPMessage *ep;
	register char *ap;
	void clear_entry();

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->parse_answer(%x)\n", s);
#endif

	clear_entry();

#ifdef DEBUG
	if (debug & D_PARSE)
		printf(" Done clearing entry\n");
#endif
	for (ep = ldap_first_entry(ld, s); ep != NULL; ep = ldap_next_entry(ld, ep)) {
#ifdef DEBUG
		if (debug & D_PARSE)
			printf(" Determining DN and name\n");
#endif
		Entry.DN = ldap_get_dn(ld, ep);
#ifdef DEBUG
		if (debug & D_PARSE)
			printf(" DN = %s\n", Entry.DN);
#endif
		rdns = ldap_explode_dn(Entry.DN, TRUE);
#ifdef DEBUG
		if (debug & D_PARSE)
			printf(" Name = %s\n", *rdns);
#endif
		Entry.name = strdup(*rdns);
		ldap_value_free(rdns);
		for (ap = ldap_first_attribute(ld, ep, &cookie); ap != NULL; ap = ldap_next_attribute(ld, ep, cookie)) {

#ifdef DEBUG
			if (debug & D_PARSE)
				printf("parsing ap = %s\n", ap);
#endif
			if ((idx = attr_to_index(ap)) < 0) {
				printf("  Unknown attribute \"%s\"\n", ap);
				continue;
			}
			add_value(&(Entry.attrs[idx]), ep, ap);
		}
	}
#ifdef DEBUG
	if (debug & D_PARSE)
		printf(" Done parsing entry\n");
#endif
}

add_value(attr, ep, ap)
struct attribute *attr;
LDAPMessage *ep;
char *ap;
{
	register int i = 0;
	char **vp, **tp, **avp;

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->add_value(%x, %x, %s)\n", attr, ep, ap);
#endif
	vp = (char **) ldap_get_values(ld, ep, ap);

	/*
	 *  Fill in the attribute structure for this attribute.  This
	 *  stores away the values (using strdup()) and the count.  Terminate
	 *  the list with a NULL pointer.
	 *
	 *  attr->quipu_name has already been set during initialization.
	 */
	if ((attr->number_of_values = ldap_count_values(vp)) > 0) {
		attr->values = (char **) Malloc((unsigned) ((attr->number_of_values + 1) * sizeof(char *)));
		avp = attr->values;

		for (i = 1, tp = vp; *tp != NULL; i++, tp++) {
#ifdef DEBUG
			if (debug & D_PARSE)
				printf("  value #%d  %s\n", i, *tp);
#endif
			/*
			 *  The 'name' field of the Entry structure already has
			 *  has the first part of the DN copied into it.  Thus,
			 *  we don't need to save it away here again.  Also, by
			 *  tossing it away here, we make printing this info out
			 *  a bit easier later.
			 */
			if (!strcmp(ap, "cn") && !strcmp(*tp, Entry.name)) {
				attr->number_of_values--;
				continue;
			}
			*avp++ = strdup(*tp);
		}
		*avp = NULL;
	}
	ldap_value_free(vp);
}

print_an_entry()
{
	int n = 0, i, idx;
	char is_a_group, **order;
	char *sub_list[MAX_VALUES], buf[SMALL_BUF_SIZE];
	extern int col_size, isaurl(), isadn();
	static char *time2text();

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->print_an_entry()\n");
#endif
	printf(" \"%s\"\n", Entry.name);
	
	/*
	 *  If the entry is a group, find all of the subscribers to that
	 *  group.  A subscriber is an entry that *points* to a group entry,
	 *  and a member is an entry that is included as part of a group
	 *  entry.
	 *
	 *  We also need to select the appropriate output format here.
	 */
	is_a_group = isgroup();
	if (is_a_group) {
		order = (char **) group_attr_print_order;
		n = find_all_subscribers(sub_list, Entry.DN);
#ifdef DEBUG
		if (debug & D_PRINT)
			printf(" Group \"%s\" has %d subscribers\n", 
								Entry.name, n);
#endif
	}
	else
		order = (char **) person_attr_print_order;

	for (i = 0; order[i] != NULL; i++) {
		idx = attr_to_index(order[i]);
#ifdef DEBUG
		if (debug & D_PRINT) {
			printf("  ATTR #%2d = %s [%s] (%d values)\n", i + 1,
				Entry.attrs[idx].output_string,
				Entry.attrs[idx].quipu_name,
				Entry.attrs[idx].number_of_values);
		}
#endif
		if (idx < 0)
			continue;
		if (Entry.attrs[idx].number_of_values == 0)
			continue;
		if (isadn(order[i]))
			print_DN(Entry.attrs[idx]);
		else if (isaurl(order[i]))
			print_URL(Entry.attrs[idx]);
		else if (isadate(order[i])) {
			/* fix time and date, then call usual routine */
			Entry.attrs[idx].values[0] = 
				time2text(Entry.attrs[idx].values[0], FALSE);
			print_values(Entry.attrs[idx]);
		}
		else
			print_values(Entry.attrs[idx]);
	}

	/*
	 *  If it is a group, then we should print the subscriber list (if
	 *  there are any).  If there are a lot of them, prompt the user
	 *  before printing them.
	 */
	if (is_a_group && (n > 0)) {
		char *label = "Subscribers:         ";

		if (n > TOO_MANY_TO_PRINT) {
			printf("  There are %d subscribers.  Print them? ", n);
			fflush(stdout);
			fetch_buffer(buf, sizeof(buf), stdin);
			if (!((buf[0] == 'y') || (buf[0] == 'Y')))
				return;
		}
		format2((char *) my_ldap_dn2ufn(sub_list[n - 1]), label, (char *) NULL, 2, 
						2 + strlen(label) + 1, col_size); 
		for (n--; n > 0; n--)
			format2((char *) my_ldap_dn2ufn(sub_list[n - 1]), (char *) NULL, 
				(char *) NULL, 2 + strlen(label), 
				2 + strlen(label) + 2, col_size); 
	}

	return;
}

#define OUT_LABEL_LEN	20

/* prints the values associated with an attribute */
print_values(A)
struct attribute A;
{
	register int i, k;
	register char *cp, **vp;
	char out_buf[MED_BUF_SIZE], *padding = NULL;
	int lead;

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->print_values(%x)\n", A);
#endif
	if (A.number_of_values == 0)
		return;
	if ((vp = A.values) == NULL)
		return;

	/*
	 *  Pad out the output string label so that it fills the
	 *  whole field of length OUT_LABEL_LEN.
	 */
	out_buf[0] = '\0';
	i = OUT_LABEL_LEN - strlen(A.output_string);
	if (i < 0) {
		printf("Output string for \"%s\" is too long.  Maximum length is %d characters\n", A.quipu_name, OUT_LABEL_LEN);
		return;
	}
	if (isgroup() && !strcmp(A.quipu_name, "mail") && (Entry.attrs[attr_to_index("member")].number_of_values == 0)) {
		A.output_string = "Members";
		i = OUT_LABEL_LEN - strlen(A.output_string);
		padding = (char *) Malloc((unsigned) (i + 1));
		(void) memset(padding, ' ', i);
		*(padding + i) = '\0';
		sprintf(out_buf, "%s:%s", A.output_string, padding);
	}
	else if (!(isgroup() && !strcmp(A.quipu_name, "mail") && (Entry.attrs[attr_to_index("member")].number_of_values > 0))) {
		padding = (char *) Malloc((unsigned) (i + 1));
		(void) memset(padding, ' ', i);
		*(padding + i) = '\0';
		sprintf(out_buf, "%s:%s", A.output_string, padding);
	}
	/*
	 *  If this happens to be a group, then do not print the output
	 *  string if we have already printed out some members.
	 */
	else if (isgroup() && !strcmp(A.quipu_name, "mail") && (Entry.attrs[attr_to_index("member")].number_of_values > 0)) {
		padding = (char *) Malloc((unsigned) (OUT_LABEL_LEN + 2));
		(void) memset(padding, ' ', OUT_LABEL_LEN + 1);
		*(padding + OUT_LABEL_LEN + 1) = '\0';
		sprintf(out_buf, "%s", padding);
	}
	lead = strlen(out_buf) + 2;

	printf("  %s", out_buf);
	for (i = 0; *vp != NULL; i++, vp++) {
		if (i > 0) {
			if (!strncmp(A.output_string, "Home a", 6) || !strncmp(A.output_string, "Business a", 10)) {
				printf("  %s", out_buf);
			}
			else {
				for (k = lead; k > 0; k--)
					putchar(' ');
			}
		}
		for (cp = *vp; *cp != '\0'; cp++) {
			switch (*cp) {
			case '$' :
				if (!strncmp(A.output_string, "Home a", 6) || !strncmp(A.output_string, "Business a", 10) || !strcmp(A.quipu_name, "multiLineDescription")) {
					putchar('\n');
					for (k = lead; k > 0; k--)
						putchar(' ');
					while (isspace(*(cp + 1)))
						cp++;
				}
				else
					putchar(*cp);
				break;
			case '\n' :
				putchar('%');
				putchar('\n');
				break;
			default:
				putchar(*cp);
			}
		}
		putchar('\n');
	}
	if (padding != NULL)
		Free(padding);
	return;
}

/* prints the DN's associated with an attribute */
print_DN(A)
struct attribute A;
{
	int i, lead;
	register char **vp;
	char out_buf[MED_BUF_SIZE], *padding = NULL;
	extern int col_size;

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->print_DN(%x)\n", A);
#endif
	if (A.number_of_values == 0)
		return;
	/*
	 *  Pad out the output string label so that it fills the
	 *  whole field of length OUT_LABEL_LEN.
	 */
	i = OUT_LABEL_LEN - strlen(A.output_string);
	if (i > 0) {
		padding = (char *) Malloc((unsigned) (i + 1));
		(void) memset(padding, ' ', i);
		*(padding + i) = '\0';
		sprintf(out_buf, "%s:%s", A.output_string, padding);
		(void) Free(padding);
	}
	lead = strlen(out_buf) + 2;

	vp = A.values;
	format2((char *) my_ldap_dn2ufn(*vp), out_buf, (char *) NULL, 2, lead + 1, col_size); 
	for (vp++; *vp != NULL; vp++) {
		format2((char *) my_ldap_dn2ufn(*vp), (char *) NULL, (char *) NULL, lead, 
			lead + 1, col_size); 
	}
	return;
}

void clear_entry()
{
	register int i;
	extern struct attribute attrlist[];

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->clear_entry()\n");
	if ((debug & D_PRINT) && (Entry.name != NULL))
		printf(" Clearing entry \"%s\"\n", Entry.name);
#endif
	if (Entry.DN != NULL)
		Free(Entry.DN);
	if (Entry.name != NULL)
		Free(Entry.name);
	Entry.may_join = FALSE;
	Entry.subscriber_count = -1;
	Entry.DN = Entry.name = NULL;

	/*  clear all of the values associated with all attributes */
	for (i = 0; attrlist[i].quipu_name != NULL; i++) {
#ifdef DEBUG
		if (debug & D_PRINT)
			printf(" Clearing attribute \"%s\" -- ", 
				Entry.attrs[i].quipu_name);
#endif
		if (Entry.attrs[i].values == NULL) {
#ifdef DEBUG
			if (debug & D_PRINT)
				printf(" no values, skipping\n");
#endif
			continue;
		}
#ifdef DEBUG
		if (debug & D_PRINT)
			printf(" freeing %d values\n", 
					Entry.attrs[i].number_of_values);
#endif
		Entry.attrs[i].number_of_values = 0;
		ldap_value_free(Entry.attrs[i].values);
		Entry.attrs[i].values = (char **) NULL;

		/*
		 *  Note:  We do not clear either of the char * fields
		 *  since they will always be applicable.
		 */
	}
}

attr_to_index(s)
char *s;
{
	register int i;
	extern struct attribute attrlist[];

	for (i = 0; attrlist[i].quipu_name != NULL; i++)
		if (!strcasecmp(s, attrlist[i].quipu_name))
			return(i);
	return(-1);
}

void initialize_attribute_strings()
{
	register int i;
	extern struct entry Entry;
	extern struct attribute attrlist[];

	for (i = 0; attrlist[i].quipu_name != NULL; i++)
		Entry.attrs[i].quipu_name = attrlist[i].quipu_name;
	for (i = 0; attrlist[i].quipu_name != NULL; i++)
		Entry.attrs[i].output_string = attrlist[i].output_string;
}

/* prints the URL/label pairs associated with an attribute */
print_URL(A)
struct attribute A;
{
	int i, lead;
	register char **vp;
	char out_buf[MED_BUF_SIZE], *padding = NULL;

#ifdef DEBUG
	if (debug & D_TRACE)
		printf("->print_URL(%x)\n", A);
#endif
	if (A.number_of_values == 0)
		return;
	/*
	 *  Pad out the output string label so that it fills the
	 *  whole field of length OUT_LABEL_LEN.
	 */
	i = OUT_LABEL_LEN - strlen(A.output_string);
	if (i > 0) {
		padding = (char *) Malloc((unsigned) (i + 1));
		(void) memset(padding, ' ', i);
		*(padding + i) = '\0';
		sprintf(out_buf, "%s:%s", A.output_string, padding);
	}
	lead = strlen(out_buf) + 2;

	vp = A.values;
	print_one_URL(*vp, 2, out_buf, lead);
	for (vp++; *vp != NULL; vp++)
		print_one_URL(*vp, lead, (char *) NULL, lead);
	if (padding != NULL)
		Free(padding);
	return;
}

print_one_URL(s, label_lead, tag, url_lead)
char *s;
int label_lead;
char *tag;
int url_lead;
{
	register int i;
	char c, *cp, *url;
	extern int col_size;
	extern void Free();

	for (cp = s; !isspace(*cp) && (*cp != '\0'); cp++)
		;
	c = *cp;
	*cp = '\0';
	url = strdup(s);
	*cp = c;
	if (*cp != '\0') {
		for (cp++; isspace(*cp); cp++)
			;
	}
	else
		cp = "(no description available)";
	format2(cp, tag, (char *) NULL, label_lead, label_lead + 1, col_size);
	for (i = url_lead + 2; i > 0; i--)
		printf(" ");
	printf("%s\n", url);
	Free(url);
}


#define GET2BYTENUM( p )	(( *p - '0' ) * 10 + ( *(p+1) - '0' ))

static char *
time2text( char *ldtimestr, int dateonly )
{
    struct tm		t;
    char		*p, *timestr, zone, *fmterr = "badly formatted time";
    time_t		gmttime;
    static long		gtime();

    memset( (char *)&t, 0, sizeof( struct tm ));
    if ( strlen( ldtimestr ) < 13 ) {
	return( fmterr );
    }

    for ( p = ldtimestr; p - ldtimestr < 12; ++p ) {
	if ( !isdigit( *p )) {
	    return( fmterr );
	}
    }

    p = ldtimestr;
    t.tm_year = GET2BYTENUM( p ); p += 2;
    t.tm_mon = GET2BYTENUM( p ) - 1; p += 2;
    t.tm_mday = GET2BYTENUM( p ); p += 2;
    t.tm_hour = GET2BYTENUM( p ); p += 2;
    t.tm_min = GET2BYTENUM( p ); p += 2;
    t.tm_sec = GET2BYTENUM( p ); p += 2;

    if (( zone = *p ) == 'Z' ) {	/* GMT */
	zone = '\0';	/* no need to indicate on screen, so we make it null */
    }

    gmttime = gtime( &t );
    timestr = ctime( &gmttime );

    timestr[ strlen( timestr ) - 1 ] = zone;	/* replace trailing newline */
    if ( dateonly ) {
	strcpy( timestr + 11, timestr + 20 );
    }

    Free ( ldtimestr );
    return( strdup( timestr ) );
}


/* gtime.c - inverse gmtime */

#if !defined( MACOS ) && !defined( _WIN32 ) && !defined( DOS )
#include <sys/time.h>
#endif /* !MACOS */

/* gtime(): the inverse of localtime().
	This routine was supplied by Mike Accetta at CMU many years ago.
 */

int	dmsize[] = {
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

#define	dysize(y)	\
	(((y) % 4) ? 365 : (((y) % 100) ? 366 : (((y) % 400) ? 365 : 366)))

#define	YEAR(y)		((y) >= 100 ? (y) : (y) + 1900)

/*  */

static long	gtime ( struct tm *tm )
{
    register int    i,
                    sec,
                    mins,
                    hour,
                    mday,
                    mon,
                    year;
    register long   result;

    if ((sec = tm -> tm_sec) < 0 || sec > 59
	    || (mins = tm -> tm_min) < 0 || mins > 59
	    || (hour = tm -> tm_hour) < 0 || hour > 24
	    || (mday = tm -> tm_mday) < 1 || mday > 31
	    || (mon = tm -> tm_mon + 1) < 1 || mon > 12)
	return ((long) -1);
    if (hour == 24) {
	hour = 0;
	mday++;
    }
    year = YEAR (tm -> tm_year);

    result = 0L;
    for (i = 1970; i < year; i++)
	result += dysize (i);
    if (dysize (year) == 366 && mon >= 3)
	result++;
    while (--mon)
	result += dmsize[mon - 1];
    result += mday - 1;
    result = 24 * result + hour;
    result = 60 * result + mins;
    result = 60 * result + sec;

    return result;
}
