/* routines for the linked list of struct user_struct's */

#include "includes.h"


void add_list_struct(int, struct user_struct *current, char *str);
void free_struct_list(struct user_struct *index);
struct user_struct * init_linked_list();
extern void fill_user_struct(int, char *, struct user_struct *);
struct user_struct * remove_from_list(struct user_struct *record);
struct user_struct * rewind_list(struct user_struct *record);
void print_and_prune(struct user_struct *record, FILE *outlist);
extern void printuser(struct user_struct *, FILE *);
void build_linked_list(int, struct user_struct *, FILE *);
extern int isvalid_userline(int, char *);
struct user_struct * filter_disabled(struct user_struct *head, FILE *outlist);
struct user_struct * setup_linked_list(int, FILE *pwlist, FILE *outlist);
void nt_ify_list(struct user_struct *head);
void print_hits(struct user_struct *head, FILE *outlist);
struct user_struct * prune_list(struct user_struct *head);
void swap_structs(struct user_struct *, struct user_struct *);
extern int crackntdialect(struct user_struct *Ustruct, char *passwd, int check_case);


void add_list_struct(int pwdump, struct user_struct *current, char *str){
	struct user_struct *ptr, *newrecord;
	
	ptr = current;

	/* if this is the first then the username
	   will not be filled in yet. */
	if (strlen(ptr->username) == 0){
		fill_user_struct(pwdump, str, ptr);
		return;
	}

  /* put the list in order based upon the value of the LANMAN hash -
     that way, when we are walking the list looking for comparisons
     we can stop as soon as the value is greater than what we have
     so we don't keep walking. */

	while (ptr->next != NULL)
		ptr = ptr->next;
	
	newrecord = (struct user_struct *)malloc(sizeof(struct user_struct));
	if (newrecord == NULL){
		fprintf(stderr, "out of memory...add_user_struct newrecord\n");
		exit(1);
	}
	fill_user_struct(pwdump, str, newrecord);
	ptr->next = newrecord;
	newrecord->previous = ptr;
	newrecord->next = NULL;
}

/* I don't know why the hell I walk through the damn thing and then go
   backwards. Sigh, since it's only called once in the main prog it
   isn't a significant performance hit */
void free_struct_list(struct user_struct *index){

	/* go to the end of the list */
	while (index->next != NULL)
		index = index->next;

	/* if the end of the list is the beginning of the list
	   free it and return */
	if (index->previous == NULL){
		free(index);
		return;
	}

	/* work backwards free()ing as we go */
	while (index->previous != NULL){
		index = index->previous;
		free(index->next);
		index->next == NULL;
	}
	/* we are at the beginning - free it */
	free(index);
}

struct user_struct * init_linked_list(){
	struct user_struct *newstruct;

	newstruct = (struct user_struct *)malloc(sizeof(struct user_struct));
	if (newstruct == NULL){
		fprintf(stderr, "out of memory...init_linked_list newstruct\n");
		exit(1);
	}
	memset(newstruct, '\0', sizeof(struct user_struct));
	return(newstruct);
}

/* removes a node from the list and returns the head of the list */
struct user_struct * remove_from_list(struct user_struct *record){
	struct user_struct *tmp, *tmp2;

	if (record == NULL)
		return(NULL);

	/* first record is only record in list */
	if ((record->next == NULL) && (record->previous == NULL)){
		free(record);
		return(NULL);
	}

	/* last record */
	if (record->next == NULL){
		tmp = record->previous;
		free(record);
		tmp->next = NULL;
		while (tmp->previous != NULL)
			tmp = tmp->previous;
		return(tmp);
	}
	
	/* first record */
	if (record->previous == NULL){
		tmp = record->next;
		tmp->previous = NULL;
		free(record);
		return(tmp);
	}

	/* middle record */
	tmp = record->previous;
	tmp2 = record->next;
	tmp->next = tmp2;
	tmp2->previous = tmp;
	free(record);
	return(tmp);
}

struct user_struct * rewind_list(struct user_struct *record){

	struct user_struct *hold;

	hold = record;

	/* accident grabber */
	if (record == NULL)
		return(NULL);

	/* it's the first node being handed in */
	if (record->previous == NULL)
		return(record);

	/* it's not the first node */
	while (record->previous != NULL) {
		record = record->previous;
	}
	return(record);
}

void build_linked_list(int pwdump, struct user_struct *head, FILE *pwlist){

	char user_entry[MAX_STRING];

	while (fgets(user_entry, MAX_STRING, pwlist) != NULL) {
		if (isvalid_userline(pwdump, user_entry) == 0){
			fprintf(stderr, "Invalid format: %s\n", user_entry);
			continue;
		}
		add_list_struct(pwdump, head, user_entry);
	}
}

struct user_struct * filter_disabled(struct user_struct *head, FILE *outlist){
	/* weed out the disabled accounts */
	struct user_struct *index, *foo;

	index = foo = head;
	
	if (head == NULL)
		return(head);

	while (foo != NULL){
		if (strncmp(index->lmhash, "DISABLED?", 9) == 0){
			fprintf(outlist, "User [%s] account is disabled\n",
				index->username);
				fflush(outlist);
				index = remove_from_list(index);
				if (index == NULL)
					foo = NULL;
		} else {
			if (index->next == NULL){
				foo = NULL;
			} else {
				index = index->next;
			}
		}
	}
	return(rewind_list(index));
}

struct user_struct *filter_nopasswd(struct user_struct *head, FILE *outlist){
	/* weed out the accounts without passwords */
	struct user_struct *index, *foo;

	if (head == NULL)
		return(head);

	index = foo = head;
	while (foo != NULL){
		if (strncmp(index->lmhash, "NULL PASSWD?", 12) == 0){
			fprintf(outlist, "User [%s] has a NULL PASSWD\n",
				index->username);
				fflush(outlist);
				index = remove_from_list(index);
				if (index == NULL)
					foo = NULL;
		} else {
			if (index->next == NULL){
				foo = NULL;
			} else {
				index = index->next;
			}
		}
	}
	return(rewind_list(index));
}


struct user_struct * setup_linked_list(int pwdump, FILE *pwlist, FILE *outlist){

	struct user_struct *head, *index;

	head = init_linked_list();

	build_linked_list(pwdump, head, pwlist);

	/* weed out the disabled accounts - only do this for pwdump built
	   lists and not for sniffer logs. There shouldn't be any accounts
	   that need to be weeded out in the sniffer logs. 

           XXX Should
	   go through and check if it was a plaintext response here or can
	   we figure that people will be bright enough to notice that 
	   on their own when they build the sniffer log??? */

	if (pwdump){
	  index = head;

	  head = filter_disabled(index, outlist);

	  index = head;

	  head = filter_nopasswd(index, outlist);
	}

	return(head);
}

void nt_ify_list(struct user_struct *head){
  struct user_struct *index;

  index = head;
  while (index){
    if (index->lmdone == 1)
      crackntdialect(index, index->lmpasswd, 1);
    index = index->next;
  }
}

void print_hits(struct user_struct *head, FILE *outlist){
	struct user_struct *index;

	index = head;
	while (index){
		if (index->lmdone == 1)
			printuser(index, outlist);
		index = index->next;
	}
}

struct user_struct * prune_list(struct user_struct *head){
	struct user_struct *foo, *tmp, *hold;

	foo = tmp = head;
	
	if (head == NULL)
		return(NULL);

	/* find the first node that isn't done */
	while (foo->lmdone != 0){
		tmp = foo->next;
		if (tmp == NULL)
			break; /* they are all done */
		foo = tmp;
	}
	
	if (!tmp){ /* they are all done - zorch the list */
		free_struct_list(head);
		return(NULL);
	}

	foo = head; /* set index to point to the head */

	while (foo != tmp){ /* while index isn't our first none done
						     member... */
		hold = foo->next;
		remove_from_list(foo);
		foo = hold;
	}

	head = tmp; /* set our new head to point to the first none done
				   member */

	while (tmp){
		if (tmp->lmdone == 1){
			hold = tmp->next;
			remove_from_list(tmp);
			tmp = hold;
		} else {
			tmp = tmp->next;
		}
	}

	return(head);
}
		
void swap_structs(struct user_struct *a, struct user_struct *b){
  struct user_struct hold;

  /* copy a to hold */

  memcpy(&hold, a, sizeof(hold));
 
  /* copy b to a */

  memcpy(a, b, sizeof(hold));

  /* copy hold to b */

  memcpy(&hold, b, sizeof(hold));

}
