#include <stdio.h>
#include <string.h>
#include <unistd.h>		/* for mktemp() */

#include "global.h"


#include "manhat-lib/shared_util.h"
#include "manhat-lib/shared_read_infofile.h"
#include "manhat-lib/shared_translate_url.h"
#include "manhat-lib/shared_file_util.h"
#include "manhat-lib/shared_access.h"
#include "manhat-lib/shared_post_inbox.h"
#include "manhat-lib/shared_lock.h"
#include "manhat-lib/shared_cs_util.h"
#include "manhat-lib/shared_news_util.h"
#include "manhat-lib/shared_fold_file.h"
#include "manhat-lib/shared_system_data.h"

RECEIVER_NODE *to_head = 0;
RECEIVER_NODE *cc_head = 0;
RECEIVER_NODE *bcc_head = 0;

FILE_NODE *file_head = 0;	/*list of attached files */
int attachments = 0;		/* how many files are attached? */




static void
read_parameters (char *course, char *key,
		 char *fromprog, int *fwd_files,
		 char *fwd_sender, char *fwd_inf_fname, int *width)
{
  char temp[25];


  cs_get_required_parameter ("crs", course, MAX_PATH);
  cs_get_required_parameter ("id", key, MAX_KEY);
  cs_get_required_parameter ("from", fromprog, MAX_PATH);
  cs_get_required_parameter ("width", temp, 20);
  *width = atoi(temp);

  /* check if there MAY BE some files forwarded */
  cs_get_optional_parameter ("files", temp, 24);
  if(strlen(temp))
      *fwd_files = atoi (temp);
  else
      *fwd_files = 0;

  if (*fwd_files)		/* if files are to be forwarded, these parameters are cs_get_required_parameterd */
    {
      cs_get_required_parameter ("sender", fwd_sender, MAX_USERNAME);
      cs_get_required_parameter ("inf", fwd_inf_fname, MAX_FILENAME);
    }

}


static void
verify_non_empty (CONFIG_STRUCT *conf)
{
  char to[MAX_PATH +1];
  FILE *fp;
  int i;
  char name[MAX_PATH +1];
  char filename[MAX_PATH +1];
  struct stat buf;
  int has_attachments = 0;

  cs_get_required_parameter("to",to, MAX_PATH); 
  cs_get_required_parameter("subject",to, MAX_PATH); 
  for(i =1; i <= conf->mail_attachments; i++)
  {
       snprintf(name, MAX_PATH, "file%d", i);
       cs_get_optional_parameter(name, filename, MAX_PATH);
       if(strlen(filename))
       {
          fp = cgi_filehandle(global_cgi, name);
          if(fstat(fileno(fp), &buf))
	        cs_critical_error (ERR_STAT_FAILED, filename);
	   if(buf.st_size ==0)
	        cs_critical_error (ERR_EMPTY_FILE, filename);
           has_attachments = 1;
       }
  }

 if(has_attachments)
   check_incomplete_upload();   /* shared_news_util.c */
}



static int
in_to_list (char *username)
{
  RECEIVER_NODE *ptr;

  for (ptr = to_head; ptr; ptr = ptr->next)
    if (!strcmp (ptr->username, username))
      return 1;

  return 0;
}



static int
in_cc_list (char *username)
{
  RECEIVER_NODE *ptr;

  for (ptr = cc_head; ptr; ptr = ptr->next)
    if (!strcmp (ptr->username, username))
      return 1;

  return 0;
}





/*** SHOULD THIS FUNCTION BE SCRAPPED?
***  BCC's haven't been used in years
***/
void
clean_bcc_list ()
{
  RECEIVER_NODE *ptr, *ptr2;

  ptr = bcc_head;
  while (ptr)
    if (in_to_list (ptr->username) || in_cc_list (ptr->username))
      {
	if (ptr == bcc_head)
	  {
	    ptr2 = ptr;
	    bcc_head = bcc_head->next;
	    free (ptr2);
	    ptr = bcc_head;
	  }
	else
	  {
	    for (ptr2 = bcc_head; ptr2->next != ptr; ptr2 = ptr2->next);
	    ptr2->next = ptr->next;
	    free (ptr);
	    ptr = ptr2->next;
	  }
      }
    else
      ptr = ptr->next;
}



static void
clean_cc_list ()
{
  RECEIVER_NODE *ptr, *ptr2;

  ptr = cc_head;
  while (ptr)
    if (in_to_list (ptr->username))
      {
	if (ptr == cc_head)
	  {
	    ptr2 = ptr;
	    cc_head = cc_head->next;
	    free (ptr2);
	    ptr = cc_head;
	  }
	else
	  {
	    for (ptr2 = cc_head; ptr2->next != ptr; ptr2 = ptr2->next);
	    ptr2->next = ptr->next;
	    free (ptr);
	    ptr = ptr2->next;
	  }
      }
    else
      ptr = ptr->next;
}



static void
build_receiver_lists ()
{
  create_rec_list ("to", &to_head);
  create_rec_list ("cc", &cc_head);


  clean_cc_list ();

}


static void
free_receiver_lists ()
{
  RECEIVER_NODE *ptr;

  while (to_head)
    {
      ptr = to_head;
      to_head = to_head->next;
      free (ptr);
    }

  while (cc_head)
    {
      ptr = cc_head;
      cc_head = cc_head->next;
      free (ptr);
    }


  while (bcc_head)
    {
      ptr = bcc_head;
      bcc_head = bcc_head->next;
      free (ptr);
    }


}


static void
free_file_list (void)
{
  FILE_NODE *ptr;

  while (file_head)
    {
      ptr = file_head;
      file_head = file_head->next;
      free (ptr);
    }


}


static void
get_unique_fname (char *unique_fname, const char *ext, time_t * time_sent,
                  CONFIG_STRUCT *conf, SESSION *user)
{

  static int n = 0;
  char timestring[MAX_TIMESTRING + 1];
  char filedir[MAX_PATH + 1];
  char filepath[MAX_PATH + 1];

  snprintf (filedir, MAX_PATH + 1, "%s%s/%s/%s/", conf->course_path, PEOPLE_DIR, user->username, MAIL_DIR);
  strftime (timestring, MAX_TIMESTRING, "%m%d%Y%H%M%S", localtime (time_sent));


  do
    {
      n++;
      snprintf (unique_fname, MAX_FILENAME + 1, "%s%d%d.%s", timestring, n, getpid(),ext);
      snprintf (filepath, MAX_PATH + 1, "%s%s", filedir, unique_fname);
    }
  while (file_exists(filepath));  /* shared_file_util.c */

}




/*static void
get_orig_fname (const char *full_orig_name, char *orig_fname)
{

  char *ptr;

  ptr = strrchr (full_orig_name, '\\');
  if (ptr)
    strncpy (orig_fname, ptr + 1, MAX_PATH + 1);
  else
    {
      ptr = strrchr (full_orig_name, '/');
      if (ptr)
	strncpy (orig_fname, ptr + 1, MAX_PATH + 1);
      else
	strncpy (orig_fname, full_orig_name, MAX_PATH + 1);
    }
}
*/



static void
get_system_filenames (const char *full_orig_name,
		      char *unique_fname,
		      char *orig_fname, time_t * time_sent,
                      CONFIG_STRUCT *conf, SESSION *user)
{

  get_unique_fname (unique_fname, "fil", time_sent, conf, user);
  get_orig_fname (full_orig_name, orig_fname);
}



static void
add_to_file_list (const char *unique_fname, const char *orig_fname)
{
  FILE_NODE *ptr;

  if (!file_head)
    {
      file_head = (FILE_NODE *) malloc (sizeof (FILE_NODE));
      if (!file_head)
	cs_critical_error (ERR_MALLOC_FAILED, "");
      ptr = file_head;
    }
  else
    {
      for (ptr = file_head; ptr->next; ptr = ptr->next);
      ptr->next = (FILE_NODE *) malloc (sizeof (FILE_NODE));
      if (!ptr->next)
	cs_critical_error (ERR_MALLOC_FAILED, "");
      ptr = ptr->next;
    }

  strncpy (ptr->unique_fname, unique_fname, MAX_PATH + 1);
  strncpy (ptr->orig_fname, orig_fname, MAX_PATH + 1);
  ptr->next = (FILE_NODE *) 0;
}


static void
write_cgi_post_file (FILE *infp, off_t size,  const char *unique_fname,
                  CONFIG_STRUCT *conf, SESSION *user)
{

#define CHUNKSIZE 2048
  FILE  *outfp;
  char filepath[MAX_PATH + 1];
  int bytes_read;
  int bytes_left;
  char buffer[CHUNKSIZE];


  snprintf (filepath, MAX_PATH + 1, "%s%s/%s/%s/%s",
	    conf->course_path, PEOPLE_DIR, user->username, MAIL_DIR, unique_fname);



  outfp = fopen (filepath, "w");
  if (!outfp)
    cs_critical_error (ERR_FOPEN_WRITE_FAILED, filepath);


  bytes_left = size;
  bytes_read = CHUNKSIZE;
  while (bytes_left)
    {
      if (bytes_left < CHUNKSIZE)
	bytes_read = bytes_left;
      if (fread (buffer, bytes_read, 1, infp) != 1)
	cs_critical_error (ERR_FREAD_FAILED, "in write_cgi_file()");

      bytes_left -= bytes_read;
      if (fwrite (buffer, bytes_read, 1, outfp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, filepath);

    }
  fclose (outfp);
#undef CHUNKSIZE

}







static void
create_link (const char *sender, const char *fwd_unique_fname,
		 const char *unique_fname, CONFIG_STRUCT *conf, SESSION *user)
{
  char linkdir[MAX_PATH + 1];
  char current_dir[MAX_PATH + 1];
  char oldpath[MAX_PATH + 1];

 
 if(!getcwd(current_dir, MAX_PATH + 1))
     cs_critical_error(ERR_GETCWD_FAILED, "in create_link()");
     
 *(current_dir + MAX_PATH) = '\0';    

  snprintf (linkdir, MAX_PATH + 1, "%s%s/%s/%s", conf->course_path,PEOPLE_DIR,
	                                                   user->username, MAIL_DIR);
  if (chdir (linkdir) == -1)
    cs_critical_error (ERR_CHDIR_FAILED, linkdir);

  snprintf (oldpath, MAX_PATH + 1, "../../%s/%s/%s", sender,
	                                           MAIL_DIR, fwd_unique_fname);


  if (link (oldpath, unique_fname) == -1)
    cs_critical_error (ERR_SYMLINK_FAILED, oldpath);


/** PROBLEM ... the next line assumes too much! */
  if (chdir (current_dir) == -1)
    cs_critical_error (ERR_ERROR_CHDIR_TO_BIN, current_dir);


}







void
process_forwarded_files (time_t * time_sent, int fwd_files,
			 char *fwd_sender, char *fwd_inf_fname,
                         CONFIG_STRUCT *conf, SESSION *user)
{
#define MAX_MSG 80

  INFO infofile_info;
  char varname[20];
  int i, found=0;
  ATTACHMENT_NODE *aptr =0;
  char fwd_unique_fname[MAX_FILENAME + 1];
  char unique_fname[MAX_FILENAME + 1];

  read_post_infofile (fwd_sender, fwd_inf_fname, &infofile_info, conf);	/* shared_read_infofile.c  */
  free_person_list ();		                                        /* shared_read_infofile.c */       

  for (i = 1; i <= fwd_files; i++)
    {
      snprintf (varname, 20, "f%d", i);
      cs_get_optional_parameter(varname, fwd_unique_fname, MAX_FILENAME);
      if(strlen(fwd_unique_fname))
      {
	  for (found = 0, aptr = attachment_head; aptr && !found; aptr = aptr->next)
	    {
	      found = !strcmp (aptr->data.unique_fname, fwd_unique_fname);
	      if (found)
		{
		  get_unique_fname (unique_fname, "fil", time_sent, conf, user);
		  add_to_file_list (unique_fname, aptr->data.orig_fname);
		  create_link(fwd_sender, fwd_unique_fname,
				                unique_fname, conf, user);
		  attachments++;
		}
	    }
	  if (!found)
	    cs_critical_error (ERR_UNIQUE_FNAME_NOT_FOUND, "in process_forwared_files()");
       }
    }


  free_attachment_list ();	/* shared_read_infofile.c */
#undef MAX_MSG
}



static void
process_attached_files (time_t * time_sent, CONFIG_STRUCT *conf, SESSION *user)
{
  char unique_fname[MAX_PATH + 1];
  char orig_fname[MAX_PATH + 1];
  char name[MAX_PATH + 1];
  char filename[MAX_PATH + 1];
  int i;
  struct stat buf; 
  FILE *fp;
  
  for(i =1; i<= conf->mail_attachments; i++)
  {
      
      snprintf(name, MAX_PATH, "file%d", i);
      cs_get_optional_parameter(name, filename, MAX_PATH); 
      if(strlen(filename))
      {
	fp = cgi_filehandle(global_cgi, name);
	if(fstat(fileno(fp), &buf))
	    cs_critical_error (ERR_STAT_FAILED, filename);
	get_system_filenames (filename, unique_fname, orig_fname,time_sent, conf, user);
	add_to_file_list (unique_fname, orig_fname);
	write_cgi_post_file (fp, buf.st_size, unique_fname, conf, user);
	attachments++;
      }
  }

}



static void
process_message_file (char *msg_fname, time_t * time_sent,
                      CONFIG_STRUCT *conf, SESSION *user, int width)
{
  char filepath[MAX_PATH+1];
  char *msg;
  FILE *fp;
  int len;

  msg = hdf_get_value(global_cgi->hdf, "Query.msg", 0);

  get_unique_fname (msg_fname, "msg", time_sent, conf, user);
  snprintf (filepath, MAX_PATH + 1, "%s%s/%s/mail/%s",
	    conf->course_path, PEOPLE_DIR, user->username, msg_fname);
  fp = fopen(filepath, "w");
  if(!fp)
     cs_critical_error(ERR_FOPEN_WRITE_FAILED, filepath);
  if(msg && strlen(msg))
   {
      len = strlen(msg);
      if(len < width || system_data_int("USE_TEXTAREA_HARD_WRAP"))
      {
         if(fwrite(msg, strlen(msg), 1, fp) != 1)
          cs_critical_error(ERR_FWRITE_FAILED, filepath);
      }
      else
	  write_folded(fp, msg, width);   /* shared_fold_file.c */
   }
   fclose(fp);
}


static void
write_info_file (char *msg_fname,
		 char *info_fname, char *subject, time_t time_sent,
                 CONFIG_STRUCT *conf, SESSION *user)
{
  FILE *fp;
  INFO info;
  ATTACHMENT attach;
  PERSON person;
  FILE_NODE *fptr;
  RECEIVER_NODE *rptr;

  char info_path[MAX_PATH + 1];

  get_unique_fname (info_fname, "inf", &time_sent, conf, user);

  cs_get_required_parameter("subject", subject, MAX_SUBJECT);
  strncpy (info.msg_fname, msg_fname, MAX_FILENAME + 1);
  strncpy (info.subject, subject, MAX_SUBJECT + 1);

  info.version = VERSION24;               /* flags this as a message sent with the 'new' post office module
                                          ** introduced with version 24
                                           */


  info.time_sent = time_sent;
  info.no_attachments = attachments;


  snprintf (info_path, MAX_PATH + 1, "%s%s/%s/%s/%s",
	    conf->course_path, PEOPLE_DIR,user->username, MAIL_DIR, info_fname);

  fp = fopen (info_path, "w");
  if (!fp)
    cs_critical_error (ERR_FOPEN_WRITE_FAILED, info_path);

  if (fwrite (&info, sizeof (INFO), 1, fp) != 1)
    cs_critical_error (ERR_FWRITE_FAILED, info_path);

  for (fptr = file_head; fptr; fptr = fptr->next)
    {
      strncpy (attach.unique_fname, fptr->unique_fname, MAX_FILENAME + 1);
      strncpy (attach.orig_fname, fptr->orig_fname, MAX_FILENAME + 1);
      if (fwrite (&attach, sizeof (ATTACHMENT), 1, fp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, info_path);
    }

  for (rptr = to_head; rptr; rptr = rptr->next)
    {
      strncpy (person.username, rptr->username, MAX_USERNAME + 1);
      strncpy (person.realname, rptr->realname, MAX_NAME + 1);
      person.how_received = TO;
      person.time_read = 0;
      person.time_deleted = 0;
      if (fwrite (&person, sizeof (PERSON), 1, fp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, info_path);
    }

  for (rptr = cc_head; rptr; rptr = rptr->next)
    {
      strncpy (person.username, rptr->username, MAX_USERNAME + 1);
      strncpy (person.realname, rptr->realname, MAX_NAME + 1);
      person.how_received = CC;
      person.time_read = 0;
      person.time_deleted = 0;
      if (fwrite (&person, sizeof (PERSON), 1, fp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, info_path);
    }

  for (rptr = bcc_head; rptr; rptr = rptr->next)
    {
      strncpy (person.username, rptr->username, MAX_USERNAME + 1);
      strncpy (person.realname, rptr->realname, MAX_NAME + 1);
      person.how_received = BCC;
      person.time_read = 0;
      person.time_deleted = 0;
      if (fwrite (&person, sizeof (PERSON), 1, fp) != 1)
	cs_critical_error (ERR_FWRITE_FAILED, info_path);
    }

  fclose (fp);
}




static void
update_inboxes (NEWFILE_RECORD * newinfo, RECEIVE_TYPE t, const char *course_path)
{
  RECEIVER_NODE *rptr;
  char inbox_path[MAX_PATH + 1];

  rptr = (t == TO ? to_head : t == CC ? cc_head : bcc_head);
  newinfo->how_received = t;

  for (; rptr; rptr = rptr->next)
    {
      snprintf (inbox_path, MAX_PATH + 1, "%s%s/%s/%s", 
                    course_path,PEOPLE_DIR, rptr->username, MAIL_DIR);
                    
     if(create_dirs_between(inbox_path))  /* shared_file_util.c */
      {
            strcat(inbox_path, "/");
            strcat(inbox_path, INBOX_FNAME);
      }
      else
	 cs_critical_error(ERR_MKDIR_FAILED, inbox_path);
                     
                    
      add_new_post_msg(newinfo, inbox_path);   /* shared_post_inbox.c */
    }
}






void
update_all_inboxes (char *inf_fname, char *subject, time_t time_sent,
		      CONFIG_STRUCT *conf, SESSION *user)
{
  NEWFILE_RECORD newinfo;


  strncpy (newinfo.sender, user->username, MAX_USERNAME + 1);
  strncpy (newinfo.sender_realname, user->realname, MAX_NAME + 1);
  newinfo.how_sent = NEWMSG;
  strncpy (newinfo.subject, subject, MAX_SUBJECT + 1);
  newinfo.time_sent = time_sent;

  newinfo.attachments = attachments;

  strncpy (newinfo.info_fname, inf_fname, MAX_FILENAME + 1);

  update_inboxes (&newinfo, TO, conf->course_path);
  update_inboxes (&newinfo, CC, conf->course_path);
  update_inboxes (&newinfo, BCC, conf->course_path);

}







static void
back_up (char *program)
{
  char temp[MAX_PATH + 1];

  translate_url_substitutes (temp, program, MAX_PATH + 1, 0);	/* shared_cgi_util.c */
  printf ("Location: %s\n\n", temp);
}



static void
record_in_outbox (const char *inf_fname, const char *subject,
		  time_t time_sent, CONFIG_STRUCT *conf, SESSION *user)
{
  OUTBOX_RECORD outbox;
  FILE *fp;
  RECEIVER_NODE *rptr;
  int i;
  char outbox_fname[MAX_PATH + 1];

  strncpy (outbox.info_fname, inf_fname, MAX_FILENAME + 1);
  strncpy (outbox.subject, subject, MAX_SUBJECT + 1);
  outbox.time_sent = time_sent;

  /* build list of up to MAX_TO recipients, each recipient name separated by 
     ** vertical bar character
   */
  *outbox.to = '\0';
  for (rptr = to_head, i = 1; rptr && i <= MAX_TO; rptr = rptr->next, i++)
    {
      if (i > 1)
	strcat (outbox.to, "|");
      strcat (outbox.to, rptr->realname);
    }
  if (rptr)			/* if we did not finish list */
    strcat (outbox.to, "|");	/* then end list with a vertical bar */


  outbox.how_sent = NEWMSG;

  outbox.is_sleepy = 0;
  outbox.unread = 1;

  outbox.attachments = attachments;	/* defined globally in this file */


  snprintf (outbox_fname, MAX_PATH + 1, "%s%s/%s/%s/%s",
	    conf->course_path, PEOPLE_DIR, user->username, MAIL_DIR, OUTBOX_FNAME);
  fp = fopen (outbox_fname, "a");
  if (!fp)
    cs_critical_error (ERR_FOPEN_APPEND_FAILED, outbox_fname);

  get_exclusive_lock(fileno(fp), 1);   /* shared_lock.c */
  if (fwrite (&outbox, sizeof (OUTBOX_RECORD), 1, fp) != 1)
    cs_critical_error (ERR_FWRITE_FAILED, outbox_fname);
  release_lock(fileno(fp));      /* shared_lock.c */
  fclose (fp);
}




int
main (void)
{
  char course[MAX_PATH + 1];	/* from crs=? command line */
  char key[MAX_KEY + 1];	/* from id=? command line */
  char from[MAX_PATH + 1];	/* from from=? command line */

  char msg_fname[MAX_FILENAME + 1];	/* name given to the message */
  char inf_fname[MAX_FILENAME + 1];	/* the info file name */
  time_t time_sent;
  char subject[MAX_SUBJECT + 1];
  int  width;
  int fwd_files;		/* derived from files=? cmd line */
  char fwd_sender[MAX_USERNAME + 1];	/* from sender = ? cmd line */
  char fwd_inf_fname[MAX_FILENAME + 1]; /* from inf=? cmd line */
  SESSION user;
  CONFIG_STRUCT conf;

  cs_cgi_init();
  read_parameters (course, key, from, &fwd_files, fwd_sender, fwd_inf_fname, &width);

  read_configuration_file (course, &conf);	/* shared_util.c */
  validate_key (key, &user, &conf);		/* shared_util.c */

  if(!has_write_permission(&user, &conf))   	/* shared_access.c */
      access_denied_error();                	/* shared_access.c */

  verify_non_empty (&conf);			/* make sure message is not empty */
  build_receiver_lists ();			/* who to send to */


  time_sent = time (NULL);
  process_message_file (msg_fname, &time_sent, &conf, &user, width);

  if (fwd_files)
    process_forwarded_files (&time_sent, fwd_files, fwd_sender,
			     fwd_inf_fname, &conf, &user);


  process_attached_files (&time_sent, &conf, &user);


  write_info_file (msg_fname, inf_fname, subject, time_sent, &conf, &user);
  
  update_all_inboxes (inf_fname, subject, time_sent, &conf, &user);
  
  
  
  record_in_outbox (inf_fname, subject, time_sent, &conf, &user);

  free_receiver_lists ();
  free_file_list ();
  back_up (from);
  cs_cgi_destroy();
  return 0;
}


