#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <ldap.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <dirent.h>

#include "ad_func.h"
#include "create_alias_files.h"

int sgs_debug_level=0;

char *
addto_w_newline(char *addto, char *addfrom)
{
	int x=0, y=0;
	if(addto==NULL)
	{
		addto=malloc(strlen(addfrom)+3);
		if(addto==NULL) return addto;
		addto[0]='\0';
	}else{
		addto=realloc(addto, strlen(addto)+strlen(addfrom)+3);
	}
	for(;addto[x]!='\0';x++) continue;
	if(addto[x]=='\0')
	{
		for(;addfrom[y]!='\0';) addto[x++]=addfrom[y++];
		/*addto[x++]='\r';*/
		addto[x++]='\n';
		addto[x]='\0';
	}
	return addto;
}

void
ad_repair_escapechars(char *ob)
{
	int before=0, after=0, escnum;
	char temp[3];
	temp[0]='\0'; temp[1]='\0'; temp[2]='\0';
	for(;ob[before]!='\0';before++)
	{
		if(ob[before]=='\\')
		{
			if(ob[before+1]!='\0' && ob[before+2]!='\0')
			{
				temp[0]=ob[before+1]; temp[1]=ob[before+2];
				escnum=strtol(temp, NULL, 16);
				switch(escnum)
				{
					case 34: /* " */
					case 43: /* + */
					case 44: /* , */
					case 59: /* ; */
					case 60: /* < */
					case 62: /* > */
					/* The backslash is accepted as escaped hex so do not change it */
					/*case 92: */ /*  \ */
						ob[after++]=(char)escnum;
						before+=2;
						continue;
						break;
					default:
						break;
				}
			}
		}
		ob[after++]=ob[before];
	}
	ob[after]='\0';
}

void
ad_separate_userdnparts(char **parts, char *user, char *rest)
{
	int x;

	user[0]='\0'; rest[0]='\0';
	strncpy(user, parts[0], CHAR_MAX);
	ad_repair_escapechars(user);
	
	strncpy(rest, parts[1], CHAR_MAX);
	for(x=2;parts[x]!=NULL && x<100;x++)
		sprintf(rest, "%s,%s", rest, parts[x]);
}

char *
ad_getmailaddress(LDAP *ad_conn, const char *base, const char *filter)
{
	LDAPMessage *msg, *sub_entry;
	char *attr_mail[]= {AD_MAIL_FIELD, (char *)0};
	struct timeval timeout;
	char **t, *member;
	
	timeout.tv_sec = AD_TIMEOUT_SEC; timeout.tv_usec = 0;
	if(ldap_search_st(ad_conn, base, LDAP_SCOPE_SUBTREE, filter, attr_mail, 0, &timeout, &msg)!=LDAP_SUCCESS)
	{
		if(sgs_debug_level>=3) printf("LDAP could not complete group member email address search for filter: %s\n", filter);
		return NULL;
	}
	if(ldap_count_entries(ad_conn, msg)!=1)
	{
		if(sgs_debug_level>=3) printf("LDAP could not identify single member: %s\n", filter);
		return NULL;
	}
	if((sub_entry=ldap_first_entry(ad_conn, msg))==NULL)
	{
		if(sgs_debug_level>=3) printf("Could not retrieve group member %s result entry\n", filter);
		return NULL;
	}
	if((t=ldap_get_values(ad_conn, sub_entry, AD_MAIL_FIELD))==NULL)
	{
		if(sgs_debug_level>=3) printf("LDAP returned null for the alias member email address for %s\n", filter);
		return NULL;
	}
	if(ldap_count_values(t)<1)
	{
		if(sgs_debug_level>=3) printf("LDAP returned zero entries for the alias member email address for %s\n", filter);
		return NULL;
	}
	strtok(t[0],"@");
	if(strlen(t[0])>=CHAR_MAX)
	{
		if(sgs_debug_level>=1) printf("LDAP returned an email address that exceeded %d: %s\n", CHAR_MAX, t[0]);
		return NULL;
	}
	member=malloc(strlen(t[0])+1);
	strcpy(member, t[0]);
	ldap_value_free(t);
	return member;
}

char *
ad_getgroupmembers(LDAP *ld, const char *base, const char *site, const char *group, char *email)
{
	LDAPMessage *msg, *sub_entry=NULL;
	struct timeval timeout;
	char filter[CHAR_MAX], tbase[CHAR_MAX], tempuser[CHAR_MAX], temprestofdn[CHAR_MAX];
	char *attr_member[]= {AD_MAIL_FIELD, AD_MEMBER_FIELD, (char *)0};
	char **tempm, **dnparts, *tmem, *members=NULL;
	int m;


#ifdef CONNECT_TO_LOCAL_LDAP_SERVERS
	LDAP *tld;
	int x=0, y=0, start=0;
	LDAPMessage *tmsg, *t_entry=NULL;
	char tdomain[CHAR_MAX];
	ad_defs *ad;
#endif
					
	timeout.tv_sec = AD_TIMEOUT_SEC; timeout.tv_usec = 0;
	
	if(strlen(group)+4>CHAR_MAX)
	{
		if(sgs_debug_level>=2) printf("ad_getgroupmembers: Group filter string out of bounds error: group=%s\n", group);
		return NULL;
	}
	sprintf(filter, "(&(objectClass=Group)(cn=%s))", group);
	if(strlen(base)+strlen(site)+5>CHAR_MAX)
	{
		if(sgs_debug_level>=2) printf("ad_getgroupmembers: Site specific base string out of bounds error: site=%s\n", site);
		return NULL;
	}
	
	if(site[0]=='\0') strcpy(tbase, base);
	else sprintf(tbase, "dc=%s,%s", site, base);

	if(ldap_search_st(ld, tbase, LDAP_SCOPE_SUBTREE, filter, attr_member, 0, &timeout, &msg)!=LDAP_SUCCESS)
	{
		if(sgs_debug_level>=2) printf("LDAP could not complete group member search for site: %s, group: %s\n", site, group);
		return NULL;
	}
	if(ldap_count_entries(ld, msg)<1)
	{
		if(sgs_debug_level>=2) printf("LDAP could find no match for the filter: %s\n", filter);
		return NULL;
	}
	if((sub_entry=ldap_first_entry(ld, msg))==NULL)
	{
		if(sgs_debug_level>=3) printf("Could not retrieve result entry for site: %s, group: %s\n", site, group);
		return NULL;
	}

	if((tempm=ldap_get_values(ld, sub_entry, AD_MAIL_FIELD))==NULL)
	{
		if(sgs_debug_level>=1) printf("LDAP returned null for the email for group %s\n", group);
		return NULL;
	}
	strtok(tempm[0],"@");
	if(strlen(tempm[0])>=CHAR_MAX)
	{
		if(sgs_debug_level>=1) printf("LDAP returned an email address that exceeded %d for group %s: %s\n", CHAR_MAX, group, tempm[0]);
		return NULL;
	}
	strcpy(email, tempm[0]);
	ldap_value_free(tempm);
	
	if((tempm=ldap_get_values(ld, sub_entry, AD_MEMBER_FIELD))==NULL)
	{
#ifdef CONNECT_TO_LOCAL_LDAP_SERVERS
		/*connect here to the local ldap server and retrieve the necessary entry data*/
			
		ad=get_ad_defaults();
		if(!ad->is_complete){
			if(sgs_debug_level>=0) printf("Could not retrieve active directory connection defaults\n");
			return NULL;
		}
		for(x=0;tbase[x]!='\0';x++)
		{
			if(tbase[x]=='=') { start=1; x++; }
			if(tbase[x]==',') { start=0; tdomain[y++]='.'; }
			if(start) tdomain[y++]=tbase[x];
			if(y>=CHAR_MAX) { tdomain[y]='\0'; break; }
		}
		tdomain[y]='\0';
		if(sgs_debug_level>=2) printf("Initializing connection to %s\n", tdomain);
		if((tld=ad_init(tdomain, 0, 0))==0)
		{
			if(sgs_debug_level>=0) printf("Could not initialize connection to active directory domain: %s\n", tdomain);
			return NULL;
		}
		if(ad_bind(tld, ad->general_user, ad->general_pw)!=AD_SUCCESS) 
		{
			ad_close(tld);
			if(sgs_debug_level>=0) printf("Could not bind to active directory with user: %s\n", ad->general_user);
			return NULL;
		}
		if(ldap_search_st(tld, tbase, LDAP_SCOPE_SUBTREE, filter, attr_member, 0, &timeout, &tmsg)!=LDAP_SUCCESS)
		{
			if(sgs_debug_level>=2) printf("LDAP could not complete group member search in domain: %s, for site: %s, group: %s\n", tdomain, site, group);
			ad_close(tld);
			return NULL;
		}
		if(ldap_count_entries(tld, tmsg)!=1)
		{
			if(sgs_debug_level>=2) printf("LDAP could find no match for the filter: %s, in domain: %s\n", filter, tdomain);
			ad_close(tld);
			return NULL;
		}
		if((t_entry=ldap_first_entry(tld, tmsg))==NULL)
		{
			if(sgs_debug_level>=3) printf("Could not retrieve result entry for domain: %s, site: %s, group: %s\n", tdomain, site, group);
			ad_close(tld);
			return NULL;
		}
		if((tempm=ldap_get_values(tld, t_entry, AD_MEMBER_FIELD))==NULL){
			if(sgs_debug_level>=2) printf("LDAP returned null for the group members of domain: %s, %s, %s\n", tdomain, site, group);
			ad_close(tld);
			return NULL;
		}
#endif			
	}
	for(m=0;m<ldap_count_values(tempm);m++)
	{
		if(strlen(tempm[m])>=CHAR_MAX)
		{
			if(sgs_debug_level>=3) printf("LDAP returned a user dn that exceeded %d: %s\n", CHAR_MAX, tempm[m]);
			continue;
		}
		
		/*separate and escape special characters with \hex equivalent*/
		dnparts=ldap_explode_dn(tempm[m], 0);
		
		/*form up the two parts, user and rest, escaping the special characters back to normal for AD*/
		ad_separate_userdnparts(dnparts, tempuser, temprestofdn);
		
		if(sgs_debug_level>=3) printf("Attempting to retrieve email address for %s (Escaped dn: %s,%s)\n", tempm[m], tempuser, temprestofdn);
		tmem=ad_getmailaddress(ld, temprestofdn, tempuser);
		
		if(tmem==NULL)
		{
			if(sgs_debug_level>=3) printf("Could not retrieve email address...\n");
			continue;
		}
		if(sgs_debug_level>=3) printf("Email address added to list: %s\n", tmem);
		members=addto_w_newline(members, tmem);
		
		free(tmem);
		ldap_value_free(dnparts);
	}
	ldap_value_free(tempm);
	if(members==NULL) if(sgs_debug_level>=2) printf("Could not retrieve any members for %s, %s\n", site, group);
	return members;
}

int
ad_create_alias_file(LDAP *ld, const char *base, const char *site, const char *group)
{
	char filename[CHAR_MAX];
	char *members;
	char email[CHAR_MAX];
	int fd;
	
	if((members=ad_getgroupmembers(ld, base, site, group, email))==NULL)
	{
		if(sgs_debug_level>=1) printf("Could not properly recover members for %s, %s\n", site, group);
		return AD_ERROR;
	}
	sprintf(filename, "%s/%s%s", ALIAS_DIR_HEADER, ALIAS_FILE_HEADER, email);
	if(sgs_debug_level>=3) printf("File to create: %s\n", filename);
	if((fd=open(TEMPFILENAME, O_CREAT|O_TRUNC|O_WRONLY, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))==-1)
	{
		if(sgs_debug_level>=1) printf("Could not open temp file, %s, for writing\n", TEMPFILENAME);
		return AD_ERROR;
	}
	if(write(fd, members, strlen(members))!=strlen(members))
	{
		if(sgs_debug_level>=1) printf("Could not write all group member names to: %s\n", filename);
		close(fd);
		return AD_ERROR;
	}
	close(fd);
	free(members);
	if(rename(TEMPFILENAME, filename)==-1)
	{
		if(sgs_debug_level>=1) printf("Could not move file to %s\n", filename);
		return AD_ERROR;
	}
	if(sgs_debug_level>=1) printf("Alias file successfully created in: %s\n", filename);
	return AD_SUCCESS;
}

int
ad_get_ldapdncount(char **dn)
{
	int x=0;

	for(;x<100;x++) if(dn[x]==NULL) return x;
	return 0;
}
	
sitegroup *
ad_get_sitegroups(LDAP *ld, const char *base, const char *site, const char *group, int *num_sgs)
{
	LDAPMessage *msg, *sub_entry=NULL;
	char filter[CHAR_MAX];
	char tsite[CHAR_MAX];
	char *attr_cn[]= {AD_CN_FIELD, (char *)0};
	struct timeval timeout;
	char **tempm, *tmem;
	int entry_count, s, m, base_count, sub_entry_count;
	static sitegroup sgs[MAX_SITEGROUPS];
	
	*num_sgs=0;
	timeout.tv_sec = AD_TIMEOUT_SEC; timeout.tv_usec = 0;
	strcpy(filter, "(&(objectClass=Group)(mail=*))");
	
	if(ldap_search_st(ld, base, LDAP_SCOPE_SUBTREE, filter, attr_cn, 0, &timeout, &msg)!=LDAP_SUCCESS)
	{
		if(sgs_debug_level>=1) printf("LDAP could not complete group member search\n");
		return NULL;
	}
	if((entry_count=ldap_count_entries(ld, msg))<1)
	{
		if(sgs_debug_level>=1) printf("LDAP could find no match for the filter: %s\n", filter);
		return NULL;
	}
	if(sgs_debug_level>=2) printf("LDAP search for site, %s, has recovered %d entries\n", site, entry_count);
	/*each entry is for each subdomain the dn was found in*/

	/*get the component part count from the base*/
	tempm=ldap_explode_dn(base,1);
	base_count=ad_get_ldapdncount(tempm);
	ldap_value_free(tempm);
	
	for(s=0;s<entry_count;s++)
	{
		if((sub_entry=(s==0?ldap_first_entry(ld, msg):ldap_next_entry(ld, sub_entry)))==NULL)
		{
			if(sgs_debug_level>=1) printf("Could not retrieve result entry %d\n", s);
			continue;
		}
		tmem=ldap_get_dn(ld, sub_entry);
		tempm=ldap_explode_dn(tmem,1);
		sub_entry_count=ad_get_ldapdncount(tempm)-base_count-1;
		
		if(sub_entry_count>0 && strcasecmp(AD_USERS_CONTAINER_STRING, tempm[sub_entry_count])!=0)
		{
			if(site!=NULL && strcasecmp(site, tempm[sub_entry_count])!=0)
			{
				if(sgs_debug_level>=3) printf("Entry does not match requested site %s: %s\n", site, tempm[sub_entry_count]);
				free(tmem);
				ldap_value_free(tempm);
				continue;
			}
			if(strlen(tempm[sub_entry_count])>=CHAR_MAX)
			{
				if(sgs_debug_level>=1) printf("LDAP returned a site name that exceeded %d: %s\n", CHAR_MAX, tempm[sub_entry_count]);
				continue;
			}
			strcpy(tsite, tempm[sub_entry_count]);
		}
		else 
		{
			tsite[0]='\0';
			if(sgs_debug_level>=3) printf("LDAP returned null for the site name for entry %d\n", s);
		}
		
		if((tempm=ldap_get_values(ld, sub_entry, AD_CN_FIELD))==NULL)
		{
			if(sgs_debug_level>=1) printf("LDAP returned null for the group name for entry %d\n", s);
			continue;
		}
		
		for(m=0;m<ldap_count_values(tempm);m++)
		{
			if(strlen(tempm[m])>=CHAR_MAX)
			{
				if(sgs_debug_level>=1) printf("LDAP returned a group name that exceeded %d: %s\n", CHAR_MAX, tempm[m]);
				continue;
			}
			if(group==NULL || strcasecmp(group, tempm[m])==0)
			{
				sgs[*num_sgs].site=malloc(strlen(tsite)+1);
				strcpy(sgs[*num_sgs].site,tsite);
				sgs[*num_sgs].group=malloc(strlen(tempm[m])+1);
				strcpy(sgs[(*num_sgs)++].group,tempm[m]);
				if(sgs_debug_level>=3) printf("Site: %s, Group: %s has been added to the search list\n", sgs[*num_sgs-1].site, sgs[*num_sgs-1].group);
				if(*num_sgs>=MAX_SITEGROUPS)
				{
					if(sgs_debug_level>=1) printf("ad_get_sitegroups has reached max sitegroups: %d\n", MAX_SITEGROUPS);
					ldap_value_free(tempm);
					return sgs;
				}
			}else{
				if(sgs_debug_level>=3) printf("Entry does not match requested group %s: %s\n", group, tempm[m]);
				continue;
			}
		}
		ldap_value_free(tempm);
	}
	return sgs;
}

int
ad_create_aliases(const char *site, const char *group)
{
	LDAP * ld;
	sitegroup *sgs;
	int x, num_sgs;
	ad_defs *ad=get_ad_defaults();
	
	if(!ad->is_complete){
		if(sgs_debug_level>=0) printf("Could not retrieve active directory connection defaults\n");
		return AD_ERROR;
	}
	if((ld=ad_init(ad->server, AD_USE_GC, AD_ONLY_USE_SSL))==0)
	{
		if(sgs_debug_level>=0) printf("Could not initialize connection to active directory server: %s\n", ad->server);
		return AD_ERROR;
	}
	if(ad_bind(ld, ad->general_user, ad->general_pw)!=AD_SUCCESS) 
	{
		ad_close(ld);
		if(sgs_debug_level>=0) printf("Could not bind to active directory with user: %s\n", ad->general_user);
		return AD_ERROR;
	}
	if((sgs=ad_get_sitegroups(ld, ad->basedn, site, group, &num_sgs))==NULL)
	{
		if(sgs_debug_level>=0) printf("Could not retrieve available sites and groups\n");
		ad_close(ld);
		return AD_ERROR;
	}
	for(x=0;x<num_sgs;x++)
	{
		if(ad_create_alias_file(ld, ad->basedn, sgs->site, sgs->group)!=AD_SUCCESS)
		{
			if(sgs_debug_level>=0) printf("Could not create alias file for site: %s, group: %s\n", (sgs->site!=NULL?sgs->site:"ALL"), (sgs->group!=NULL?sgs->group:"ALL"));
		}else{ 
			if(sgs_debug_level>=0) printf("Successfully created alias file for site: %s, group: %s\n", (sgs->site!=NULL?sgs->site:"ALL"), (sgs->group!=NULL?sgs->group:"ALL"));
		}
		sgs++;
	}
	ad_close(ld);
	return AD_SUCCESS;
}

void
kill_old_files(void)
{
	DIR *dir;
	struct dirent *entry;
	char dead[CHAR_MAX];

	if(sgs_debug_level>=0) printf("Looking in %s for old alias file removal\n", ALIAS_DIR_HEADER);
	if((dir=opendir(ALIAS_DIR_HEADER))==NULL)
	{
		if(sgs_debug_level>=1) printf("Could not open directory to clean: %s\n", ALIAS_DIR_HEADER);
		return;
	}
	for(;(entry=readdir(dir))!=NULL;)
	{
		if(strlen(entry->d_name)<=2) continue;
		if(strstr(entry->d_name, ALIAS_FILE_HEADER)!=NULL)
		{
			if(strlen(ALIAS_DIR_HEADER)+strlen(entry->d_name)+2>CHAR_MAX)
			{
				if(sgs_debug_level>=2) printf("To be deleted filename size is greater than %d: %s/%s.  Skipping...\n", CHAR_MAX, ALIAS_DIR_HEADER, entry->d_name);
				continue;
			}
			sprintf(dead, "%s/%s", ALIAS_DIR_HEADER, entry->d_name);
			if(sgs_debug_level>=2) printf("unlinking file: %s\n", dead);
			if(unlink(dead)==-1) if(sgs_debug_level>=2) printf("Could not unlink file: %s\n", dead);
		}else if(sgs_debug_level>=3)printf("Filename: %s/%s does not match alias file prefix %s\n", ALIAS_DIR_HEADER, entry->d_name, ALIAS_FILE_HEADER);
	}
}
void
show_usage(char *prog_name)
{
	printf("%s [-V] [-s site] [-g group] [-q] [-d 1-3] [-R] [-h]\n", prog_name);
	printf("\t-s sitename\tOnly create alias files for groups within this site.\n");
	printf("\t-g groupname\tOnly create alias files for this group name.\n");
	printf("\t-q\t\tQuiet, display no output\n");
	printf("\t-d debug level\tDebug verbosity level to display: 1-3.\n");
	printf("\t-R\t\tRemove all files in alias directory: %s\n\t\t\tprior to creating new files.\n", ALIAS_DIR_HEADER);
	printf("\t\t\tFiles must match alias header: %s to be deleted.\n", ALIAS_FILE_HEADER);
	printf("\t-V\t\tDisplays version then exits.\n");
	printf("\t-h\t\tDisplay this output.\n");
	exit(0);
}

int
main(int argc, char **argv)
{
	int x=1, kill_files=0;
	char *tsite=NULL, *tgroup=NULL;
	
	sgs_debug_level=0;
	
	if(argc<=1) printf("Creating all available alias files\n");
	else
	{
		for(;x<argc;x+=2)
		{
			if(argv[x][0]=='-')
			{
				switch(argv[x][1])
				{
					case 's':
						if(x+1<argc && argv[x+1] && strlen(argv[x+1])<CHAR_MAX) tsite=argv[x+1];
						else show_usage(argv[0]);
						break;
					case 'g':
						if(x+1<argc && argv[x+1] && strlen(argv[x+1])<CHAR_MAX) tgroup=argv[x+1];
						else show_usage(argv[0]);
						break;
					case 'd':
						if(x+1<argc && argv[x+1] && strlen(argv[x+1])<CHAR_MAX && (sgs_debug_level=atoi(argv[x+1]))>0 && sgs_debug_level<=3) printf("debug level: %d\n", sgs_debug_level);
						else show_usage(argv[0]);
						break;
					case 'q':
						sgs_debug_level=-1;
						x--;
						break;
					case 'R':
						kill_files=1;
						x--;
						break;
					case 'V':
						printf("%s version: %s\n", argv[0], AD_CURRENT_VERSION);
						exit(0);
						break;
					case 'h':
					default:
						show_usage(argv[0]);
						break;
				}			
			}else show_usage(argv[0]);
		}
	}
	if(kill_files==1) kill_old_files();
	if(ad_create_aliases(tsite, tgroup)!=AD_SUCCESS) _exit(1);
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1