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

#include "ad_func.h"

void
zero_string(char *line, int len)
{
	int x=0;
	for(;x<len;) line[x++]='\0';
}

void
strtolower(char *line, int len)
{
	int x=0;
	for(;line[x]!='\0' && x<=len;x++) if(line[x]>=65 && line[x]<=90)line[x]+=32;
	return;
}

/*begin control file read functions*/
int
read_int(const char *filename)
{
	char line[CHAR_MAX];
	int x, t, fp;
   
	if ((fp = open(filename, O_RDONLY)) == -1) return 0;
	zero_string(line, sizeof(line));
	t=read(fp, line, CHAR_MAX);
	close(fp);
	for(x=0;x<=t && x<CHAR_MAX;x++) if(!(line[x]>=48&&line[x]<=57)) break;
	line[x]='\0';
	return atoi(line);	
}

char *
read_line(const char *filename)
{
	int x, t, fp;
 	char *line;

	if ((fp = open(filename, O_RDONLY)) == -1) return NULL;
	line=(char *)malloc(CHAR_MAX);
	zero_string(line, CHAR_MAX);
	t=read(fp, line, CHAR_MAX);
	close(fp);
	for(x=0;x<=t && x<CHAR_MAX;x++) if(line[x]=='\r' || line[x]=='\n') break;
	line[x]='\0';
	return line;
}
				

ad_pwd *
getdefault_mailuser(const char *mailname)
{
	static ad_pwd ad;
	char t[CHAR_MAX];
	if((ad.uid=read_int(AD_DEFAULTUID_LOC))==0)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Could not retrieve value from file: %s", AD_DEFAULTUID_LOC);
		return &ad;
	}
	if((ad.gid=read_int(AD_DEFAULTGID_LOC))==0)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Could not retrieve value from file: %s", AD_DEFAULTGID_LOC);
		return &ad;
	}
	if((ad.home_dir=read_line(AD_DEFAULTMAILROOT_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Could not retrieve value from file: %s", AD_DEFAULTMAILROOT_LOC);
		return &ad;
	}
	if(strlen(mailname)+strlen(ad.home_dir)+1>CHAR_MAX)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Out of bounds error: %s+%s", ad.home_dir, mailname);
		return &ad;
	}
	if(CONVERT_MAILNAME_TO_LOWERCASE)
	{
		strcpy(t,mailname);
		strtolower(t, CHAR_MAX);
		strcat(ad.home_dir, t);
	}else strcat(ad.home_dir, mailname);
	if((ad.shell=read_line(AD_DEFAULTSHELL_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Could not retrieve value from file: %s", AD_DEFAULTSHELL_LOC);
		return &ad;
	}
	if(mailname==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "UserName is null");
		return &ad;
	}
	ad.user=(char *)malloc(CHAR_MAX);
	strcpy(ad.user, mailname);
	ad.is_complete=1;
	return &ad;
}

ad_defs *
get_ad_defaults(void)
{
	static ad_defs ad;
	
	if((ad.server=read_line(AD_DEFAULT_ADSERVERNAME_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "get_ad_defaults: Could not retrieve value from file: %s", AD_DEFAULT_ADSERVERNAME_LOC);
		return &ad;
	}
	if((ad.general_user=read_line(AD_DEFAULT_ADGENERALUSERDN_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "get_ad_defaults: Could not retrieve value from file: %s", AD_DEFAULT_ADGENERALUSERDN_LOC);
		return &ad;
	}
	if((ad.general_pw=read_line(AD_DEFAULT_ADGENERALUSERPASS_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "get_ad_defaults: Could not retrieve value from file: %s", AD_DEFAULT_ADGENERALUSERPASS_LOC);
		return &ad;
	}
	if((ad.basedn=read_line(AD_DEFAULT_ADBASEDN_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "get_ad_defaults: Could not retrieve value from file: %s", AD_DEFAULT_ADBASEDN_LOC);
		return &ad;
	}
	if((ad.domain=read_line(AD_DEFAULT_ADDOMAIN_LOC))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "get_ad_defaults: Could not retrieve value from file: %s", AD_DEFAULT_ADDOMAIN_LOC);
		return &ad;
	}
	ad.is_complete=1;
	return &ad;
}
/*end control file read functions*/

/*begin user/group setting functions*/
int
setup_identity(ad_pwd *ad)
{
	gid_t ga[1];
	char t[CHAR_MAX];
	
	ga[0]=(gid_t)ad->gid;
	if(setgroups(1, ga)==-1) return 0;
	if(setgid((gid_t) ad->gid)!=0) return 0;
	if(setuid((uid_t) ad->uid)!=0) return 0;
  if(chdir(ad->home_dir)!=0)
	{
		if(CREATE_HOME_DIR)
		{
			if(mkdir(ad->home_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)!=0) return 0;
			syslog(AD_LOG_TO | LOG_WARNING, "Created homedir at: %s", ad->home_dir);
			if(CREATE_MAILDIR)
			{
				if(strlen(QMAIL_MAILDIRMAKE) + strlen(ad->home_dir) + 10 > CHAR_MAX) return 0;
				sprintf(t, "%s %s/Maildir", QMAIL_MAILDIRMAKE, ad->home_dir);
				if(system(t)!=0) return 0;
				syslog(AD_LOG_TO | LOG_WARNING, "Created maildir in: %s", ad->home_dir);
  			if(chdir(ad->home_dir)!=0) return 0;
				if(system("echo \"./Maildir/\">.qmail")!=0) return 0;
				syslog(AD_LOG_TO | LOG_WARNING, "Created .qmail file in: %s", ad->home_dir);
			}
			syslog(AD_LOG_TO | LOG_WARNING, "File and home directory creation complete: %s", ad->home_dir);
		} else return 0;
	}
	
  if(setenv("USER", ad->user, 1)!=0) return 0;
  if(setenv("HOME", ad->home_dir, 1)!=0) return 0;
  if(setenv("SHELL", ad->shell, 1)!=0) return 0;
	return 1;
}

/*end user/group setting functions*/


/*begin LDAP Active Directory functions*/
int
ad_setoption(LDAP *ad_conn, int isVersion3)
{
	int version;
	if(isVersion3 != 1) {
		version = LDAP_VERSION2;
		if(ldap_set_option(ad_conn, LDAP_OPT_PROTOCOL_VERSION, &version)!=LDAP_OPT_SUCCESS) return AD_ERROR;
		else return AD_SUCCESS;
	}else{
		version = LDAP_VERSION3;
		if(ldap_set_option(ad_conn, LDAP_OPT_PROTOCOL_VERSION, &version)!=LDAP_OPT_SUCCESS) return AD_ERROR;
		else return AD_SUCCESS;
	}
}

LDAP *
ad_init(const char *server, int useGC, int useSSL)
{
	LDAP *ad_conn;
	char istring[CHAR_MAX];
	
	/* allocate the connection */
	if(strlen(server)>CHAR_MAX-15)
	{
		syslog(AD_LOG_TO | LOG_ERR, "ad_init: Overflow error: ldap%s://%s:%d", (useSSL?"s":""), server, (useGC==1?GC_SSL_PORT:LDAPS_PORT));
		return NULL;
	}
	sprintf(istring, "ldap%s://%s:%d", (useSSL?"s":""), server, (useGC==1?(useSSL?GC_SSL_PORT:GC_PORT):(useSSL?LDAPS_PORT:LDAP_PORT)));
	if(ldap_initialize(&ad_conn, istring)!=LDAP_SUCCESS)
	{
		syslog(AD_LOG_TO | LOG_ERR, "ad_init: Could not initialize %s %sconnection to AD: %s", (useGC==1?"GC":"LDAP"), (useSSL?"SSL ":""), server);
		return NULL;
	}
	if(ad_setoption(ad_conn, 1)!=AD_SUCCESS)
	{
		syslog(AD_LOG_TO | LOG_ERR, "ad_init: Could not set LDAP options");
		return NULL;
	}
	return ad_conn;
}

void
ad_close(LDAP *ad_conn)
{
	ldap_unbind_s(ad_conn);
}

int
ad_bind(LDAP *ad_conn, const char *binddn, const char *passwd)
{
	int attempt=0, res;
	
	/* connect to the LDAP server */
	for(;attempt<AD_MAX_BIND_ATTEMPT;attempt++)
		if((res=ldap_simple_bind_s(ad_conn, binddn, passwd))==LDAP_SUCCESS) return AD_SUCCESS;
	syslog(AD_LOG_TO | LOG_ERR, "AD Authentication failed with dn: %s - %s", binddn, ldap_err2string(res));
	return AD_ERROR;
}

int
ad_getsam_fulldn(LDAP *ad_conn, const char *base, const char *domain, const char *username, char *samdn)
{
	LDAPMessage *msg, *fe;
	char filter[CHAR_MAX];
	char *attr[]= {AD_USERNAME_FIELD, (char *)0};
	struct timeval timeout;
	char *dn;
	int res;
	
	timeout.tv_sec = AD_TIMEOUT_SEC;
	timeout.tv_usec = 0;
	
	if(strlen(username)+strlen(domain)+32>CHAR_MAX)
	{
		syslog(AD_LOG_TO | LOG_ERR, "ad_getsam_fulldn: Email address string out of bounds error: username=%s, domain=%s", username, domain);
		return AD_ERROR;
	}
	sprintf(filter, "(&(objectClass=User)(mail=%s%s%s))", username, (ALLOW_WILDCARDS_IN_EMAIL_ADDRESS?"@*":"@"), domain);
	
	if(ldap_search_st(ad_conn, base, LDAP_SCOPE_SUBTREE, filter, attr, 0, &timeout, &msg)!=LDAP_SUCCESS)
	{
		syslog(AD_LOG_TO | LOG_ERR, "LDAP could not complete search");
		return AD_ERROR;
	}
	if((res=ldap_count_entries(ad_conn, msg))!=1)
	{
		if(res==0)
		{
			syslog(AD_LOG_TO | LOG_ERR, "LDAP could find no match for the email address: %s%s%s", username, (ALLOW_WILDCARDS_IN_EMAIL_ADDRESS?"@*":"@"), domain);
			return AD_ERROR;
		}else{	
			syslog(AD_LOG_TO | LOG_ERR, "LDAP returned an invalid number of entries for %s%s%s (%d)",  username, (ALLOW_WILDCARDS_IN_EMAIL_ADDRESS?"@*":"@"), domain, ldap_count_entries(ad_conn, msg));
			return AD_ERROR;
		}
	}
	if((fe=ldap_first_entry(ad_conn, msg))==NULL)
	{
		syslog(AD_LOG_TO | LOG_ERR, "Could not retrieve result entry");
		return AD_ERROR;
	}
	if((dn=ldap_get_dn(ad_conn, fe))==NULL){
		syslog(AD_LOG_TO | LOG_ERR, "LDAP returned null for the dn");
		return AD_ERROR;
	}
	if(strlen(dn)>=CHAR_MAX){
		syslog(AD_LOG_TO | LOG_ERR, "DN exceeded CHAR_MAX length of %d", CHAR_MAX);
		return AD_ERROR;
	}
	strncpy(samdn, dn, CHAR_MAX);
	samdn[CHAR_MAX-1]='\0';
	return AD_SUCCESS;
}

int
ad_verify_user(const char *user, const char *pass)
{
	LDAP * ld;
	char sam[512];
	ad_defs *ad=get_ad_defaults();
	if(!ad->is_complete) return AD_ERROR;
	if((ld=ad_init(ad->server, AD_USE_GC, AD_ONLY_USE_SSL))==0) return AD_ERROR;
	if(ad_bind(ld, ad->general_user, ad->general_pw)==AD_SUCCESS)
	{
		if((ad_getsam_fulldn(ld, ad->basedn, ad->domain, user, sam))==AD_SUCCESS)
		{
			ad_close(ld);
			/*Now that we have the user, lets rebind with them to verify the password*/
			if((ld=ad_init(ad->server, AD_USE_GC, AD_ONLY_USE_SSL))!=0)
			{
				if(ad_bind(ld, sam, pass)==AD_SUCCESS)
				{
					ad_close(ld);
					return AD_SUCCESS;
				}
			}
		}
	}
	ad_close(ld);
	return AD_ERROR;
}

int
ad_getpw(const char *user)
{
	LDAP * ld;
	ad_pwd *adp;
	ad_defs *ad=get_ad_defaults();
	char ts[CHAR_MAX], output[CHAR_MAX];
	const char *t=ts;
	int x=0, y=0, z=0;
	
	if(!ad->is_complete) return AD_ERROR;
	if((ld=ad_init(ad->server, AD_USE_GC, AD_ONLY_USE_SSL))==0) return AD_ERROR;
	if(ad_bind(ld, ad->general_user, ad->general_pw)!=AD_SUCCESS) 
	{
		ad_close(ld);
		return AD_ERROR;
	}
	if((ad_getsam_fulldn(ld, ad->basedn, ad->domain, user, ts))!=AD_SUCCESS)
	{
		ad_close(ld);
		return AD_ERROR;
	}
	ad_close(ld);
	
	adp=getdefault_mailuser(user);
	if(!ad->is_complete) return AD_ERROR;
	
	for(z=0;z<=5;z++)
	{
		switch(z)
		{
		case 0:
			t=user;
			break;
		case 1:
			sprintf(ts, "%d", adp->uid);
			t=ts;
			break;
		case 2:
			sprintf(ts, "%d", adp->gid);
			t=ts;
			break;
		case 3:
			t=adp->home_dir;
			break;
		case 4:
			ts[0]='\0';
			t=ts;
		case 5:
			break;
		}
		for(y=0;t[y]!='\0' && x<CHAR_MAX-1;) output[x++]=t[y++];
		output[x++]='\0';
	}
	y=x;
	for(x=0;x<y;x++) putchar(output[x]);
	return AD_SUCCESS;
};

int
send_alias(const char *alias)
{
	char ts[CHAR_MAX], output[CHAR_MAX];
	char *t=ts;
	int x=0, y=0, z=0;
	
	for(;z<=5;z++)
	{
		switch(z)
		{
		case 0:
			t=ALIAS_NAME;
			break;
		case 1:
			sprintf(ts, "%d", ALIAS_UID);
			t=ts;
			break;
		case 2:
			sprintf(ts, "%d", ALIAS_GID);
			t=ts;
			break;
		case 3:
			t=ALIAS_DIR_HEADER;
			break;
		case 4:
			ts[0]='-';
			ts[1]='\0';
			t=ts;
			break;
		case 5:
			strcpy(ts, alias);
			t=ts;
			break;
		}
		for(y=0;t[y]!='\0' && x<CHAR_MAX-1;) output[x++]=t[y++];
		output[x++]='\0';
	}
	y=x;
	for(x=0;x<y;x++) putchar(output[x]);
	return AD_SUCCESS;
};

/*end LDAP Active Directory functions*/


syntax highlighted by Code2HTML, v. 0.9.1