/* 
   The Jitterbug report tracking system

   Copyright (C) Andrew Tridgell 1997

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.



   jitterbug.c, the main cgi-bin program. This is self-contained: once
   mail is placed in directories accessible to this program according
   to the documentation everything else is handled here.

*/

#include "jitterbug.h"

struct message_info zero_info;

static char **main_dir_list;
static char **user_list;

/* Headers that always get displayed. Must be stored in lowercase. */
static char *main_headers[] = {"from:", "to:", "date:", "cc:", "subject:", NULL};

/* markers that start a URL */
static char *url_markers[] = {"http://", "ftp://", 
			      "mailto:", "https://", NULL};


/* are they logged in as a guest? */
int guest=0;

int done_chroot = 0;

/* global cgi variables -- reflect changes in dump_globals() for debugging*/
static char *directory;
static char *directoryq; /* quoted form */
char *expression="";
char *user="";
static int page;
int selectid;
int casesensitive;
int messagetype = MTYPE_ALL;
static int numquotelines;
static int addsignature;
static int fullheaders;

/* these are the user preferences -- reflect changes in dump_globals() */
static char *textfont = "";
static char *editfont = "";
static char *signature = "";
static char *email = "";
static char *fullname = "";
static char *bgcolour = "";
static char *textcolour = "";
static char *linkcolour = "";
static char *vlinkcolour = "";
static char *alinkcolour = "";
static int notes_x = 85;
static int notes_y = 10;
static int mail_x = 85;
static int mail_y = 20;
static int messages_per_screen = 10;
static int dir_cols = 4;
int max_message_size = 5000;
static int gzip_encoding;
static int notes_lines=3;

static char *download_extension(void)
{
	if (gzip_encoding || lp_gzip_download()) 
		return GZIP_DOWNLOAD_EXTENSION;
	return DOWNLOAD_EXTENSION;
}

/* show some pre-formatted text */
static void preformatted(char *s)
{
	printf("<pre>");
	if (*textfont) {
		printf("<font %s>", textfont);
	}
	printf("%s", NS(s));
	if (*textfont) {
		printf("</font>");
	}
	printf("</pre>\n");
}


/* display a file as html. This is used for various info files
   in the system. Set html to 1 if the file contains html codes */
static int display_file(char *fname1, int html)
{
	char *data;
	char *fname = fname1;
	char *dir;

	if (*fname == '/') {
		dir = root_directory();
		fname = (char *)malloc(strlen(dir) + strlen(fname1) + 2);
		sprintf(fname,"%s%s", dir, fname1);
	}
	

	if (getuid() == 0) return 0;

	data = load_file(fname, NULL, 0);
	if (!data) {
		if (fname != fname1) free(fname);
		return 0;
	}

	if (html) {
		printf("%s\n", NS(data));
	} else {
		char *s = quotedup(data);
		preformatted(s);
		free(s);
	}

	if (fname != fname1) free(fname);
	free(data);
	return 1;
}	



/* display an email in a reasonable way */
static void display_email(char *fname)
{
	char *s, *p, *q, c;
	char *data = load_file(fname, NULL, 0);
	int i, len, remaining=0;
	if (!data) return;
	
	s = quotedup(data);
	if (!s) return;
	free(data);
	data = s;

	if (!fullheaders) {
		printf("<b><pre>");
		if (*textfont) {
			printf("<font %s>", textfont);
		}

		while ((p = strchr(s,'\n'))) {
			if (*s == '\n') {
				s++;
				break;
			}
			*p = 0;

			q = strdup(s); 
			if (!q) fatal("strdup out of memory in display_email");
			strlower(q); /*rfc822 says any case ok*/

			for (i=0;main_headers[i];i++) 
				if (strncmp(q,main_headers[i], strlen(main_headers[i])) == 0)
					printf("%s\n", s);
			*p = '\n';
			free(q);
			s = p+1;
		}
		if (*textfont) {
			printf("</font>");
		}
		printf("</pre></b>\n");
	}

	wrap(s, WRAP_COLS);

	len = strlen(s);

	if (max_message_size && len > max_message_size) {
		remaining = len - max_message_size;
		s[max_message_size] = 0;
	}
	
	printf("<pre>");
	if (*textfont) {
		printf("<font %s>", textfont);
	}

        if (!strlen(s)) {
	    printf("\nWarning: no body text for this message\n");
	}

	while (s && *s) {
		for (i=0;url_markers[i];i++) 
			if (strncmp(url_markers[i],s, 
			    strlen(url_markers[i])) == 0) 
                        {
				p = s + strlen(url_markers[i]);
				
				while ((*p) &&
				       (isalnum(*p) || 
					strchr("/.@~-_#;&?:=", *p))) {
					p++;
				}
				if (p[-1] == '.') p--;

				c = *p;
				*p = 0;

				printf("<A HREF=\"%s\">%s</A>", s, s);
				
				*p = c;
				s = p;
				break;
			}
		if (!url_markers[i]) {
			putchar(*s);
			if (*s) s++;
		}
	}
	
	if (*textfont) {
		printf("</font>");
	}
	printf("</pre>\n");

	if (remaining) {
		printf("<br><b>Message of length %d truncated</b> <input type=submit name=fullmessage value=\"Full Message\"><br>\n",
		       len);
	}

	free(data);
}


/* set the title */
void print_title(char *fmt, ...)
{
	va_list ap;  
	static int done_title;

	if (done_title) return;

	printf("Content-type: text/html\n");
	if (gzip_encoding && !cgi_variable("preferences")) {
		printf("Content-Encoding: gzip\n\n");
		cgi_start_gzip();
	} else {
		printf("\n");
	}

	html_doctype();

	printf("<HTML>\n<HEAD>\n<TITLE>\n");

	va_start(ap, fmt);
#ifdef DEBUG_COMMENTS
	printf("Debugging in HTML comments: compiled %s %s\n",
	       __TIME__, __DATE__);	
#else
	vprintf(fmt, ap);
#endif
	va_end(ap);

	printf("</TITLE>\n</HEAD>\n");

	if (!display_file("/header.html", 1)) {
		if (*bgcolour || *textcolour || *linkcolour || *vlinkcolour ||
		    *alinkcolour) {
			printf("<BODY bgcolor=\"%s\" text=\"%s\" link=\"%s\" vlink=\"%s\" alink=\"%s\">", 
			       bgcolour, textcolour, linkcolour, vlinkcolour,
			       alinkcolour);
		} else {
			printf("<BODY background=\"%s\">\n\n", 
			       NS(lp_background()));
		}
	}

	done_title = 1;
}


/* finish off the page */
static void print_footer(void)
{
	display_file("/footer.html", 1);

	printf("\n</BODY>\n</HTML>\n");
}

#ifdef DEBUG_COMMENTS

static void dump_globals(void)
{
  char dummy[100]="";
  print_title(dummy);

  printf("<!== Start global variable dump in %s ==>\n",__FILE__);
  printf("<!== General globals: ==>\n");
  printf("<!==        directory=%s",NS(directory));
  printf("<!==        expression=%s",NS(expression));
  printf("<!==        user=%s",NS(user));
  printf("<!==        page=%d",page);
  printf("<!==        selectid=%d",selectid);
  printf("<!==        casesensitive=%d",casesensitive);
  printf("<!==        messagetype=%d",messagetype);
  printf("<!==        numquotelines=%d",numquotelines);
  printf("<!==        fullheaders=%d",fullheaders);
  printf("<!==        addsignature=%d",addsignature);

  printf("<!== User preferences: ==>\n");
  printf("<!==        textfont=%s",NS(textfont));
  printf("<!==        editfont=%s",NS(editfont));
  printf("<!==        signature=%s",NS(signature));
  printf("<!==        email=%s",NS(email));
  printf("<!==        fullname=%s",NS(fullname));
  printf("<!==        bgcolour=%s",NS(bgcolour));
  printf("<!==        textcolour=%s",NS(textcolour));
  printf("<!==        linkcolour=%s",NS(linkcolour));
  printf("<!==        vlinkcolour=%s",NS(vlinkcolour));
  printf("<!==        alinkcolour=%s",NS(alinkcolour));
  printf("<!==        notes_x=%d",notes_x);
  printf("<!==        notes_y=%d",notes_y);
  printf("<!==        mail_x=%d",mail_x);
  printf("<!==        mail_y=%d",mail_y);
  printf("<!==        messages_per_screen=%d",messages_per_screen);
  printf("<!==        dir_cols=%d",dir_cols);
  printf("<!==        gzip_encoding=%d",gzip_encoding);
  printf("<!==        notes_lines=%d",notes_lines);
  printf("<!==        max_message_size=%d",max_message_size);
  printf("<!== End global variable dump in %s ==>\n",__FILE__);
}
#endif

/* panic when something goes wrong */
void fatal(char *why, ...)
{
	va_list ap;  

	print_title("%s - fatal error", NS(lp_title()));
#ifdef DEBUG_COMMENTS 
	dump_globals();
#endif

	printf("The system encountered a fatal error<p><b>\n");

	printf("<pre>\n");
	va_start(ap, why);
	vprintf(why, ap);
	va_end(ap);
	printf("</pre>\n");

	printf("</b><p>\n");

	printf("The last error code was: %s\n<p>", strerror(errno));

	if (!guest) {
		printf("uid/gid=%d/%d\n<p>", getuid(), getgid());
	}

	print_footer();
	unlock_system();
	fflush(stdout);
	exit(0);
}


/* load the global variables */
void load_globals(void)
{
	char *p;
	if ((p=getenv("PATH_INFO"))) {
		if (*p == '/') p++;
		if (*p) {
			directory = strdup(p);
			unquote(directory);
			/* keep the quoted form available */
			directoryq = urlquote(p); 
		}
	}
	
	if ((p=cgi_variable("expression")) && *p) {
		expression = p;
	}

	if (guest) {
		user = "guest";
	} else {
		if ((p=cgi_variable("user")) && *p) {
		       user = p;
		       if (!valid_user(user)) fatal("invalid username");
		}

		if (lp_group_authentication()) {
			p = getenv("REMOTE_USER");
			if (!user_list)
				user_list = load_file_list("users",valid_user);
			if (p && valid_user(p) && in_list(user_list, p)) {
				user = p;
			}
		}
	}

	html_input_hiddenivar(page);
	html_input_hiddenivar(selectid);
	html_input_hiddenivar(numquotelines);
	html_input_hiddenivar(casesensitive);
	html_input_hiddenivar(addsignature);
	html_input_hiddenivar(fullheaders);
	html_input_hiddenivar(messagetype);
}



/* a string of global variables for urls */
static char *url_globals(void)
{
	static char buf[1000];
	char *p=buf;

	if (strlen(expression) < 100 && expression && *expression) {
		sprintf(p,"expression=%s;",urlquote(expression));
		p += strlen(p);
	}

 	if (user && *user) {
		sprintf(p, "user=%s;", user);
		p += strlen(p);
	}

	p += html_url_ivar(p, selectid);
	p += html_url_ivar(p, numquotelines);
	p += html_url_ivar(p, casesensitive);
	p += html_url_ivar(p, addsignature);
	p += html_url_ivar(p, fullheaders);
	p += html_url_ivar(p, messagetype);

	p = pulldown_url_globals(p);

	if (p != buf) {
		p--;
		*p = 0;
	}

	if (p == buf) {
		return "null=0";
	}

	quote_spaces(buf);
	return buf;
}


/* setup globals as hidden variables */
void hidden_globals(void)
{
	html_hiddenvar(expression);
	html_hiddenvar(user);
	html_hiddenivar(page);
	html_hiddenivar(selectid);
	html_hiddenivar(numquotelines);
	html_hiddenivar(casesensitive);
	html_hiddenivar(addsignature);
	html_hiddenivar(fullheaders);
	html_hiddenivar(messagetype);
	pulldown_hiddens();
}


/* get the curent page being viewed as a string */
static char *current_page(void)
{
	static char buf[1000];
	char *s1 = getenv("SCRIPT_NAME");
	char *s2 = directoryq;
	if (!s2) return NS(s1);
	if (strlen(s1) + strlen(s2) > 900)
		fatal("string too long");
	sprintf(buf, "%s/%s", s1, s2);
	return buf;
}


/* show the standard search buttons */
static void show_search_buttons(void)
{
	char sid[20]="";

	printf("\n<hr>\n");
	       
	printf("<table border=0>\n");

	printf("<tr><td>Case sensitive:</td>\n");
	printf("<td><input type=radio name=casesensitive value=1 %s>yes&nbsp;\n",casesensitive?"CHECKED":"");
	printf("<input type=radio name=casesensitive value=0 %s>no</td>\n",!casesensitive?"CHECKED":"");

        printf("<td>Regular expression</td><td><input size=20 type=text name=expression value=\"%s\"></td></tr>\n",quotequotes(expression));

        printf("<tr><td>Message type:</td><td>\n");
	printf("<input type=radio name=messagetype value=\"%d\" %s>all&nbsp;\n",
	       MTYPE_ALL, messagetype==MTYPE_ALL?"CHECKED":"");
	printf("<input type=radio name=messagetype value=\"%d\" %s>pending&nbsp;\n",
	       MTYPE_PENDING, messagetype==MTYPE_PENDING?"CHECKED":"");
	printf("<input type=radio name=messagetype value=\"%d\" %s>replied&nbsp;\n",
	       MTYPE_REPLIED, messagetype==MTYPE_REPLIED?"CHECKED":"");
	printf("<input type=radio name=messagetype value=\"%d\" %s>unreplied&nbsp;\n",
	       MTYPE_UNREPLIED, messagetype==MTYPE_UNREPLIED?"CHECKED":"");
	printf("</td>\n");

	if (selectid)
          sprintf(sid,"%d", selectid);

	printf("<td>Select message id:<td><input size=5 type=text name=selectid value=\"%s\"></td></tr>\n",
	       sid);

	printf("</table>\n");

	display_pulldowns(NULL);
	
	html_input_submit("Filter", "Select Messages");

	printf("<hr>\n");
}


static void not_as_guest(char *action)
{
	printf("<b>You may not perform the action %s as a guest user</b><p>", action);
}


/* this makes the whole system fairly secure. If anyone does gain access
   they can only get at the bug reports files */
static void do_chroot(void)
{
	char *p, *user;
	char *dir;
	time_t t;
	char buf[10];
	gid_t gid;
	uid_t uid;

	p = getenv("AUTH_TYPE");
	if (!p || !*p) {
		guest = 1;
	}
	
	user = lp_auth_user();
	if (user && *user) {
		p = getenv("REMOTE_USER");
		if (!p || strcmp(p, user)) {
			guest = 1;
		}
	}

	if (guest) {
		gid = interpret_gid(lp_guest_gid());
		uid = interpret_uid(lp_guest_uid());
	} else {
		gid = interpret_gid(lp_gid());
		uid = interpret_uid(lp_uid());
	}


	/* this looks pointless, but it is needed in order for the
	   C library on some systems to fetch the timezone info
	   before the chroot */
	t = time(NULL);
	localtime(&t);

	/* this also seems pointless - see slprintf.c for an explanation */
	slprintf(buf, sizeof(buf), "xx");

	dir = lp_chroot_directory();

	if (dir && *dir) {
		if (chdir(dir)) {
			fatal("failed to chdir(%s)", dir);
		}

		if (strcmp(dir,"/") && (chroot(dir) || chdir("/"))) {
			fatal("failed to chroot(%s)", dir);
		}
	}

	done_chroot = 1;

	dir = lp_base_directory();

	if (chdir(dir)) {
		fatal("failed to chdir(%s)", dir);
	}

	if (getgid() != gid && setgid(gid)) {
		fatal("failed to set gid %d", (int)gid);
	}
	if (getuid() != uid && setuid(uid)) {
		fatal("failed to set uid %d", (int)uid);
	}
}




/* quote a email - putting in > characters at the start of each line */
static char *quote_email(char *raw)
{
	int i, count;
	char *ret, *raw1;
	char *p;

	raw1 = raw = strdup(raw);
	if (!raw) fatal("strdup out of memory in quote_email");

	wrap(raw, WRAP_COLS);

	p = strstr(raw,"\n\n");
	if (p) raw = p+2;

	for (i=count=0;raw[i];i++)
		if (raw[i] == '\n') count++;
	
	ret = malloc(i + count*3 + 10);

	if (!ret) fatal("malloc out of memory in quote_email");

	p = ret;
	strcpy(p,"\n> ");
	p += 3;

	while (*raw) {
		if (*raw == '\n') {
			if (raw[1] != '>') {
				strcpy(p,"\n> ");
				p += 3;
			} else {
				strcpy(p,"\n>");
				p += 2;
			}
			raw++;
		} else {
			*p++ = *raw++;
		}
	}

	*p = 0;

	free(raw1);

	return ret;
}


/* display a editable text box */
static void textarea(char *name, int rows, int cols, char *s)
{
	if (*editfont) {
		printf("<font %s>", editfont);
	}
	printf("<textarea name=%s rows=%d cols=%d wrap=physical>%s</textarea>\n",
	       name, rows, cols, s);
	if (*editfont) {
		printf("</font>");
	}
}


/* free up an info structure loaded by get_info */
void free_info(struct message_info *info)
{
	if (!info->loaded) return;

	if (info->from) free(info->from);
	if (info->subject) free(info->subject);
	if (info->date) free(info->date);
	if (info->raw) free(info->raw);
	info->loaded = 0;
}


/* add a audit message */
void add_audit(char *name,char *msg, ...)
{
	va_list ap;
	char buf[1024];
	FILE *f;

	va_start(ap, msg);

	if (!guest)
		lock_system();

	check_overflow(strlen(name) + strlen(root_directory()) + 10, sizeof(buf));

	if (*name == '/')
		sprintf(buf,"%s%s.audit", root_directory(), name);
	else
		sprintf(buf,"%s.audit", name);

	f = fopen(buf, "a");
	if (f) {
		fprintf(f,"%s\t%s\t", timestring(), user);
		vfprintf(f,msg,ap);
		fprintf(f,"\n");
		fclose(f);
	}

	va_end(ap);

	if (!guest)
		unlock_system();

	if (!guest && !f) {
		fatal("failed to open audit file %s.audit", name);
	}
}


static void show_directories(void)
{
	int i;

	printf("<A HREF=\"%s?%s\">Up to top level</A><br>\n",
	       getenv_null("SCRIPT_NAME"), url_globals());

	for (i=0;main_dir_list[i];i++) {
		printf("<A HREF=\"%s/%s?%s\">%s</A>&nbsp;&nbsp;\n",
		       getenv_null("SCRIPT_NAME"),
		       urlquote(main_dir_list[i]), 
		       url_globals(),
		       main_dir_list[i]);
	}
	printf("<p><b>Logged in as %s</b><p>\n", user);
}



/* this displays the message audit trail */
static void view_audit(void)
{
	char *name = cgi_variable("auditid");
	char buf[100];
	struct message_info info = zero_info;
	if (!get_info(name, &info, 0)) fatal("can't get info in view_audit");

	show_directories();

	printf("<form method=POST action=\"%s\">\n", current_page());
	printf("<p>Audit trail of %s/%s \n", directory, name);
	printf("<input type=submit name=return value=\"View Message\"><p>\n");

	check_overflow(strlen(name)+10,sizeof(buf));

	sprintf(buf,"%s.audit", name);
	printf("<hr>\n");
	display_file(buf, 0);
	printf("<hr>\n");
	
	hidden_globals();
	printf("<input type=hidden name=id value=\"%s\">\n", name);	
	printf("</form>\n");
}


/* this displays the system audit trail */
static void view_system_audit(void)
{
	char *fname = "system.audit";

	show_directories();

	if (guest) {
		not_as_guest("view_system_audit");
		return;
	}

	printf("<form method=POST action=\"%s\">\n", current_page());
	printf("<p>System audit trail\n");

	printf("<hr>\n");
	display_file(fname, 0);
	printf("<hr>\n");
	
	hidden_globals();
	printf("</form>\n");
}

static void download_options(char *name)
{
	printf("<A HREF=\"%s?%s;download=%s/%s%s\">Download message</A><br>\n",
	       current_page(), url_globals(),
	       directoryq, name, download_extension());
}


/* this displays the message view/edit screen */
static void view_message(char *name)
{
	struct message_info info = zero_info;
	char *s;
	int j, i;

	if (!get_info(name, &info, 0)) fatal("can't get info in view_message");

	show_directories();

	printf("<form method=POST action=\"%s\">\n", current_page());
	printf("<p>Viewing <a href=\"#themesg\">%s/%s</a>", directory, name);
	printf("<input type=submit name=refresh value=Refresh><br>\n");
	printf("Full headers <input type=checkbox name=fullheaders value=1 %s><br>",fullheaders?"CHECKED":"");

	{
		/*ensure checkbox always has a value - see doc*/
		int headers_saved = fullheaders;
		fullheaders = 0;
		hidden_globals();
		fullheaders = headers_saved;
	}
	printf("<input type=hidden name=id value=\"%s\">\n", name);	
	printf("</form>\n");


	printf("<b>From: %s<br>Subject: %s<br></b>\n",
	       NS(info.from), NS(info.subject));

	printf("<A HREF=\"%s?compose=%s;%s\">Compose reply</A><br>\n",
	       current_page(), name, url_globals());

	download_options(name);

#if MESSAGE_LINKS
	printf("<A HREF=\"%s?links=%s;%s\">View/edit linked messages</A><br>\n",
	       current_page(),name,url_globals());
#endif

	printf("<form method=POST action=\"%s\">\n", current_page());

	printf("<input type=hidden name=changeid value=\"%s\">\n", name);

	printf("Move To:\n");
	html_select_list(main_dir_list, xsprintf("moveto.%s", name), directory);
	printf("<br>\n");

	s = quotedup(load_ext(name, "notes"));
	wrap(s, WRAP_COLS);
	printf("<A HREF=\"#replies\">%d replies</A>: ",
	       i = count_replies(name, NULL));
	for (j = 1; j <= i; j++)
		printf("<A HREF=\"#reply%d\">%d</A> ", j, j);
	printf("<br>\n");
	printf("<A HREF=\"#followups\">%d followups</A>: ",
	       i = count_followups(name, NULL));
	for (j = 1; j <= i; j++)
		printf("<A HREF=\"#followup%d\">%d</A> ", j, j);
	printf("<p>\n");

	display_pulldowns(name);

	printf("Private message: <input type=radio name=private value=1 %s>yes&nbsp;\n",
	       info.private?"CHECKED":"");
	printf(" <input type=radio name=private value=0 %s>no<p>\n",
	       !info.private?"CHECKED":"");

	printf("Notes:<br>\n");
	textarea("notes", notes_y, notes_x, s);
	free(s);

	printf("<br>Notification: <input type=text size=30 name=msg_notification value=\"%s\">\n",
	       msg_notification(name));

	printf("<br><input type=submit name=msg_change Value=\"Submit Changes\">\n");
	printf("<input type=hidden name=auditid value=\"%s\">\n", name);

	printf("<input type=submit name=audit Value=\"View Audit Trail\"><br>\n");

	hidden_globals();

	printf("</form>\n");

	printf("<form method=POST action=\"%s\">\n", current_page());
	hidden_globals();
	printf("<input type=hidden name=id value=\"%s\"><br>\n", name);

	printf("<a name=\"themesg\"</a>\n");
	display_email(name);

	i = j = 1;
	while (i<MAX_REPLIES || j<MAX_REPLIES) {
		char buf1[100];
		char buf2[100];
		struct stat st1, st2;
		int r1, r2;
		int doreply=0;

		check_overflow(strlen(name)+20, sizeof(buf1));

		sprintf(buf1,"%s.reply.%d", name, i);
		sprintf(buf2,"%s.followup.%d", name, j);

		r1 = stat(buf1, &st1);
		r2 = stat(buf2, &st2);
		if (r1 && r2) break;

		if (!r1 && !r2) {
			if (st1.st_mtime < st2.st_mtime) {
				doreply = 1;
			} else {
				doreply = 0;
			}
		} else if (!r1) {
			doreply = 1;
		} else {
			doreply = 0;
		}

		if (doreply) {
			if (i == 1) printf("<a name=replies></a>\n");
			printf("<hr>\n");
			printf("<a name=\"reply%d\"></a>\n", i);
			printf("<H3>Reply %d</H3>\n", i);
			printf("<A HREF=\"%s?resend=%s;id=%s;%s\">Resend</A><br>\n",
			       current_page(), buf1, name, url_globals());
			display_email(buf1);
			printf("<hr>\n");
			i++;
			continue;
		}

		if (j == 1) printf("<a name=followups></a>\n");
		printf("<hr>\n");
		printf("<a name=\"followup%d\"></a>\n", j);
		printf("<H3>Followup %d</H3>\n", j);
		printf("<A HREF=\"%s?compose=%s;followup=%d;%s\">Compose reply</A><br>\n",
		       current_page(), name, j, url_globals());

		download_options(buf2);

		display_email(buf2);
		printf("<hr>\n");
		j++;
	}

	printf("</form>\n");

	show_directories();
}


static void send_mail(char *from, char *to, char *cc, char *bcc, char *subject,
		      char *body, char *sig)
{
	int fd;

	fd = smtp_start_mail(from, to, cc, bcc, subject, strlen(body)+2000);
	if (fd == -1) fatal(smtp_error());

	if (subject) {
		smtp_write(fd, "X-Loop: %s\n", lp_from_address());
		smtp_write(fd, "\n");
	}
	
	smtp_write_data(fd, body);
	if (sig)
		smtp_write_data(fd, sig);
	if (smtp_end_mail(fd) == -1)
		fatal(smtp_error());
}
		


static void send_bug_report(void)
{
	char *to = lp_from_address();
	char *from = cgi_variable("from");
	char *subject = cgi_variable("subject");
	char *body = cgi_variable("body");
	char *privates = cgi_variable("private");
	int fd, private=0;
	int i;
	char *vname, *value;

	if (!to || !from || !*from || !body || !*body || 
	    !subject || !*subject) 
		fatal("you must fill in all fields");

	if (strlen(from) > 500 || strlen(to) > 500 || 
	    strlen(subject) > 500)
		fatal("internal overflow sending bug report");

	wrap(body, WRAP_COLS);

	fd = smtp_start_mail(from, to, NULL, NULL, NULL, strlen(body) + 2000);
	if (fd == -1) fatal(smtp_error());

	if (privates && atoi(privates))
		private = 1;

	smtp_write(fd,"From: %s\n", from);
	smtp_write(fd,"To: %s\n", to);
	smtp_write(fd,"Subject: %s%s\n", 
		   private?"PRIVATE: ":"",
		   subject);
	smtp_write(fd, "\n");

	for (i=0; (value=cgi_vnum(i, &vname)); i++) {
		if (strncmp(vname, "opt_", 4)) continue;

		smtp_write(fd, "%s: %s\n", vname+4, value);
	}

	smtp_write(fd, "Submission from: %s (%s)\n",
		   getenv_null("REMOTE_HOST"), getenv_null("REMOTE_ADDR"));
	if (!guest)
		smtp_write(fd,"Submitted by: %s\n", user);

	smtp_write(fd, "\n\n");

	smtp_write_data(fd, body);

	smtp_end_mail(fd);

	printf("<b>Submission successful</b><p>\n");
}


static void send_reply(void)
{
	int i;
	char *to = cgi_variable("to");
	char *from = cgi_variable("from");
	char *subject = cgi_variable("subject");
	char *cc = cgi_variable("cc");
	char *bcc = cgi_variable("bcc");
	char *body = cgi_variable("body");
	char *name = cgi_variable("id");
	FILE *f;

	if (guest) {
		not_as_guest("send_reply");
		return;
	}

	if (!to || !from || !body || !subject) fatal("no message to send");

	if (strlen(from) > 500 || strlen(to) > 500 || 
	    (cc && strlen(cc) > 500) || 
	    (bcc && strlen(bcc) > 500) || 
	    strlen(subject) > 500)
		fatal("internal overflow while sending message");


	wrap(body, WRAP_COLS);

	send_mail(from, to, cc, bcc, subject, body, 
		  addsignature?signature:NULL);

	lock_system();

	/* now save this reply in a .reply file */
	for (i=1;i<MAX_REPLIES;i++) {
		int fd;
		time_t t = time(NULL);
		char buf[100];

		check_overflow(strlen(name)+20, sizeof(buf));

		sprintf(buf,"%s.reply.%d", name, i);
		
		fd = open(buf,O_WRONLY|O_CREAT|O_EXCL, 0644);
		if (fd == -1) continue;

		add_audit(name, "sent reply %d", i);

		f = fdopen(fd, "w");
		if (!f) fatal("failed to fdopen in send_reply");
		fprintf(f,"From: %s\n", from);
		fprintf(f,"To: %s\n", to);
		fprintf(f,"Subject: %s\n", subject);
		fprintf(f,"Date: %s", ctime(&t));

		if (cc && *cc)
			fprintf(f,"CC: %s\n", cc);
		fprintf(f,"\n%s", body);
		if (addsignature && signature)
			fprintf(f,"%s", signature);
		fclose(f);
		close(fd);

		break;
	}

	unlock_system();

	printf("<b>Mail sent successfully</b><p>\n");
}


static void resend_reply(void)
{
	char *to;
	char *from;
	char *cc, *raw;
	char *name = cgi_variable("resend");
	char *id = cgi_variable("id");

	if (guest) {
		not_as_guest("resend_reply");
		return;
	}

	if (!name) fatal("no message to resend");

	raw = load_file(name, NULL, 0);
	if (!raw) fatal("can't open file in resend_reply");

	to = getmailheader(raw, "To:", 0);
	cc = getmailheader(raw, "CC:", 0);
	from = getmailheader(raw, "From:", 0);

	if (cc) {
		if (*cc == '<') cc++;
		if (cc[strlen(cc)-1] == '>') cc[strlen(cc)-1] = 0;
	}

	if (!to || !from) fatal("can't parse message in resend_reply");

	send_mail(from, to, cc, NULL, NULL, raw, NULL);

	add_audit(id, "resent %s", name);

	printf("<b>Mail resent successfully</b><p>\n");
}



/* work out the from address for a reply - it can come form
   ether the info field or the From field of a followup */
static char *get_from(struct message_info *info, char *name)
{
	char *r, *ret=NULL;
	char buf[100];
	char *followup;

	if (info->raw)
		ret = getmailheader(info->raw, "Reply-To:", 1);
	if (!ret)
		ret = info->from;

	ret = extract_address(ret);

	followup = cgi_variable("followup");
	if (!followup) 
		return ret;

	check_overflow(strlen(name) + strlen(followup) + 20,sizeof(buf));

	sprintf(buf,"%s.followup.%s", name, followup);
	r = load_file(buf, NULL, 0);
	if (!r) return ret;

	ret = getmailheader(r, "Reply-To:", 1);
	if (!ret)
		ret = getmailheader(r, "From:", 1);
	if (!ret)
		ret = getmailheader(r, "Return-Path:", 1);
	if (!ret) fatal("can't parse from address");

	free(r);

	return extract_address(ret);
}


/* work out what text to quote */
static char *get_quote(struct message_info *info, char *name)
{
	char *followup;
	char *ret;

	if (guest) return "";

	if (!cgi_variable("doquote"))
		return "";

	followup = cgi_variable("followup");
	if (followup) {
		char *r;
		char buf[100];
		check_overflow(strlen(name) + strlen(followup) + 20,sizeof(buf));
		sprintf(buf,"%s.followup.%s", name, followup);
		r = load_file(buf, NULL, 0);
		if (!r) fatal("can't load file in get_quote");
		ret = quote_email(r);
	} else {
		ret = quote_email(info->raw);
	}

	if (numquotelines) {
		int i=numquotelines+1;
		char *p=ret;
		while (i-- && p && *p) {
			p = strchr(p, '\n');
			if (p) p++;
		}
		
		if (p) *p = 0;
	}

	return ret;
}


static char *faq_dir(void)
{
	static char buf[1024];
	check_overflow(strlen(root_directory()) + strlen(FAQ_DIR) + 10,sizeof(buf));
	sprintf(buf,"%s/%s", root_directory(), FAQ_DIR);
	return buf;
}

/* work out what body to include in a mail - could be a quote of a FAQ */
static char *get_body(struct message_info *info, char *name)
{
	char *p;
	char fname[200];
	char *body = cgi_variable("body");

	if (!body) body = "";

	if (cgi_variable("doquote")) return get_quote(info, name);

	if (cgi_variable("loadfaq") && (p=cgi_variable("faq"))) {
		check_overflow(strlen(faq_dir())+strlen(p)+10, sizeof(fname));
		sprintf(fname,"%s/%s", faq_dir(), p);
		p = load_file(fname,NULL,0);
		if (p) return p;
	}

	if (cgi_variable("savefaq") && (p=cgi_variable("faq")) &&
	    !strstr(p,"..")) {
		check_overflow(strlen(faq_dir())+strlen(p)+10, sizeof(fname));
		sprintf(fname,"%s/%s", faq_dir(), p);
		save_file(fname,body);
		add_audit(SYSTEMFILE,"saved FAQ %s", p);
	}

	return body;
}

static void save_preferences(void)
{
	FILE *f;
	char buf[100];

	if (guest) {
		not_as_guest("save_preferences");
		return;
	}

	check_overflow(strlen(user)+10, sizeof(buf));
	sprintf(buf,"%s.prefs", user);

	f = fopen(buf,"w");
	if (!f) {
	  fatal("Can't open preferences file \"%s.prefs\" for write",user);
	}

	fprintf(f,"signature=%s&", urlquote(signature));
	fprintf(f,"email=%s&", urlquote(email));
	fprintf(f,"fullname=%s&", urlquote(fullname));
	fprintf(f,"notesx=%d&", notes_x);
	fprintf(f,"notesy=%d&", notes_y);
	fprintf(f,"mailx=%d&", mail_x);
	fprintf(f,"maily=%d&", mail_y);
	fprintf(f,"textfont=%s&", urlquote(textfont));
	fprintf(f,"editfont=%s&", urlquote(editfont));
	fprintf(f,"messagespscreen=%d&", messages_per_screen);
	fprintf(f,"dircols=%d&", dir_cols);
	fprintf(f,"gzipencoding=%d&", gzip_encoding);
	fprintf(f,"noteslines=%d&", notes_lines);
	fprintf(f,"maxmessagesize=%d&", max_message_size);
	fprintf(f,"bgcolour=%s&", bgcolour);
	fprintf(f,"textcolour=%s&", textcolour);
	fprintf(f,"linkcolour=%s&", linkcolour);
	fprintf(f,"vlinkcolour=%s&", vlinkcolour);
	fprintf(f,"alinkcolour=%s&", alinkcolour);

	fclose(f);
}


static void load_preferences(void)
{
	FILE *f;
	char buf[100];
	char *p;

	check_overflow(strlen(user)+10, sizeof(buf));
	sprintf(buf,"%s.prefs", user);

	f = fopen(buf,"r");
	if (f) cgi_load_variables(f, NULL);

	if (!f && guest) return;

	if ((p=cgi_variable("signature"))) {
		signature = p;
	}

	if ((p=cgi_variable("email"))) {
		email = p;
	}

	if ((p=cgi_variable("fullname"))) {
		fullname = p;
	}

	if ((p=cgi_variable("bgcolour"))) {
		bgcolour = p;
	}

	if ((p=cgi_variable("textfont"))) {
		textfont = p;
	}

	if ((p=cgi_variable("editfont"))) {
		editfont = p;
	}

	if ((p=cgi_variable("textcolour"))) {
		textcolour = p;
	}

	if ((p=cgi_variable("linkcolour"))) {
		linkcolour = p;
	}

	if ((p=cgi_variable("vlinkcolour"))) {
		vlinkcolour = p;
	}

	if ((p=cgi_variable("alinkcolour"))) {
		alinkcolour = p;
	}

	if ((p=cgi_variable("notesx"))) {
		notes_x = atoi(p);
		if (notes_x < 1) notes_x = 1;
	}

	if ((p=cgi_variable("notesy"))) {
		notes_y = atoi(p);
		if (notes_y < 1) notes_y = 1;
	}

	if ((p=cgi_variable("mailx"))) {
		mail_x = atoi(p);
		if (mail_x < 1) mail_x = 1;
	}

	if ((p=cgi_variable("maily"))) {
		mail_y = atoi(p);
		if (mail_y < 1) mail_y = 1;
	}

	if ((p=cgi_variable("messagespscreen"))) {
		messages_per_screen = atoi(p);
		if (messages_per_screen < 1) 
			messages_per_screen = 1;
	}

	if ((p=cgi_variable("dircols"))) {
		dir_cols = atoi(p);
		if (dir_cols < 1) dir_cols = 1;
	}

	if ((p=cgi_variable("gzipencoding"))) {
		gzip_encoding = atoi(p);
	}

	if ((p=cgi_variable("noteslines"))) {
		notes_lines = atoi(p);
		if (notes_lines < 1) notes_lines = 1;
	}

	if ((p=cgi_variable("maxmessagesize"))) {
		max_message_size = atoi(p);
		if (max_message_size < 1000) max_message_size = 1000;
	}

	if (f) fclose(f);
}


/* edit the user preferences */
static void edit_preferences(void)
{
	show_directories();

	printf("<form method=POST action=\"%s\">\n", current_page());

	printf("<p>Editing preferences for %s\n", user);
	printf("<input type=submit name=commit Value=Commit>\n");
	printf(" <input type=submit name=preferences Value=Refresh><br>\n");

	printf("<p><table border=0>\n");
	printf("<tr><td>Full name: </td><td><input type=text size=60 name=fullname value=\"%s\"></td></tr>\n", fullname);
	printf("<tr><td>Email address: </td><td><input type=text size=60 name=email value=\"%s\"></td></tr>\n", email);
	printf("<tr><td>gzip encoding: </td><td><input type=text size=5 name=gzipencoding value=\"%d\"></td></tr>\n", gzip_encoding);
	printf("<tr><td>notes lines: </td><td><input type=text size=5 name=noteslines value=\"%d\"></td></tr>\n", notes_lines);
	printf("<tr><td>Notes columns: </td><td><input type=text size=5 name=notesx value=\"%d\"></td></tr>\n", notes_x);
	printf("<tr><td>Notes rows: </td><td><input type=text size=5 name=notesy value=\"%d\"></td></tr>\n", notes_y);
	printf("<tr><td>Mailer columns: </td><td><input type=text size=5 name=mailx value=\"%d\"></td></tr>\n", mail_x);
	printf("<tr><td>Mailer rows: </td><td><input type=text size=5 name=maily value=\"%d\"></td></tr>\n", mail_y);
	printf("<tr><td>Messages/screen: </td><td><input type=text size=5 name=messagespscreen value=\"%d\"></td></tr>\n", messages_per_screen);
	printf("<tr><td>Directory columns: </td><td><input type=text size=5 name=dircols value=\"%d\"></td></tr>\n", dir_cols);
	printf("<tr><td>Maximum message size: </td><td><input type=text size=8 name=maxmessagesize value=\"%d\"></td></tr>\n", max_message_size);

	printf("<tr><td>Background Colour: </td><td><input type=text size=10 name=bgcolour value=\"%s\"></td></tr>\n", bgcolour);
	printf("<tr><td>Text Colour: </td><td><input type=text size=10 name=textcolour value=\"%s\"></td></tr>\n", textcolour);
	printf("<tr><td>Link Colour: </td><td><input type=text size=10 name=linkcolour value=\"%s\"></td></tr>\n", linkcolour);
	printf("<tr><td>Active Link Colour: </td><td><input type=text size=10 name=alinkcolour value=\"%s\"></td></tr>\n", alinkcolour);
	printf("<tr><td>Visited Link Colour: </td><td><input type=text size=10 name=vlinkcolour value=\"%s\"></td></tr>\n", vlinkcolour);
	printf("<tr><td>Text Font: </td><td><input type=text size=30 name=textfont value=\"%s\"></td></tr>\n", textfont);
	printf("<tr><td>Edit Font: </td><td><input type=text size=30 name=editfont value=\"%s\"></td></tr>\n", editfont);

	printf("</table>\n");

	printf("Signature:<br>\n");
	textarea("signature", 8, 80, signature);

	printf("<p>Editing preferences for %s\n", user);
	printf("<input type=submit name=commit Value=Commit>\n");
	printf(" <input type=submit name=preferences Value=Refresh>\n");

	hidden_globals();
	printf("</form>\n");
}


/* compose a bug report */
static void compose_bug_report(void)
{
	show_directories();

	printf("<form method=POST action=\"%s\">\n", current_page());

	display_file("/reportform.html", 1);

	hidden_globals();

	printf("</form>\n");
}

/* compose a reply to a message */
static void compose_reply(void)
{
	struct message_info info = zero_info;
	char *name = cgi_variable("compose");
	char *subject = cgi_variable("subject");
	char *to = cgi_variable("to");
	char *from = cgi_variable("from");
	char *cc = cgi_variable("cc");
	char *bcc = cgi_variable("bcc");
	char **faq_list=NULL;
	char *faq = cgi_variable("faq");
	char *newfaq = cgi_variable("newfaq");
	char *body = cgi_variable("body");
	char *p;

	if (guest) {
		not_as_guest("compose_reply");
		return;
	}

	if (!name) fatal("no message specified");

	if (!get_info(name, &info, 0)) fatal("can't get info");

	if (!to) to = get_from(&info, name);
	if (!from) {
		from = (char *)malloc(strlen(fullname) + 
				      strlen(lp_from_address()) + 10);
		if (!from) fatal("out of memory\n");
		sprintf(from,"%s <%s>", fullname, lp_from_address());
	}
	if (!cc) cc = "";
	if (!bcc) bcc = "";

	show_directories();

	printf("<form method=POST action=\"%s\">\n", current_page());

	printf("<input type=hidden name=id value=\"%s\">\n", name);

	printf("<p>Replying to %s/%s<p>\n", directory, name);

	printf("<table border=0>\n");
	printf("<tr><td>From: </td><td><input type=text size=60 name=from value=\"%s\"></td></tr>\n", from);
	printf("<tr><td>To: </td><td><input type=text size=60 name=to value=\"%s\"></td></tr>\n", to);
	printf("<tr><td>CC: </td><td><input type=text size=60 name=cc value=\"%s\"></td></tr>\n", cc);
	printf("<tr><td>BCC: </td><td><input type=text size=60 name=bcc value=\"%s\"></td></tr>\n", bcc);
	if (!subject) {
		if (strstr(info.subject, lp_pr_identifier())) {
			printf("<tr><td>Subject: </td><td><input type=text size=60 name=subject value=\"%s%s\"></td></tr>\n",
			       strncasecmp(info.subject,"Re:", 3) == 0?"":"Re: ",
			       quotequotes(info.subject));
		} else {
			printf("<tr><td>Subject: </td><td><input type=text size=60 name=subject value=\"%s%s (%s%s)\"></td></tr>\n",
			       strncasecmp(info.subject,"Re:", 3) == 0?"":"Re: ",
			       quotequotes(info.subject), lp_pr_identifier(), name);
		}
	} else {
		printf("<tr><td>Subject: </td><td><input type=text size=60 name=subject value=\"%s\"></td></tr>\n", quotequotes(subject));
	}

	printf("</table>\n");


	if (!is_directory(faq_dir())) mkdir(faq_dir(),0755);

	if (cgi_variable("createfaq") && (p=cgi_variable("newfaq")) &&
	    !strstr(p,"..")) {
		char fname[200];
		check_overflow(strlen(faq_dir()) + strlen(p) + 10, sizeof(fname));
		sprintf(fname,"%s/%s", faq_dir(), p);
		save_file(fname,body?body:"");
		faq = newfaq;
		add_audit(SYSTEMFILE,"created FAQ %s", faq);
	}
	

	/* generate the FAQ controls */
	faq_list = load_dir_list(faq_dir(), NULL);
	printf("<br>FAQ: ");
	html_select_list(faq_list, "faq", faq);
	printf("<input type=submit name=loadfaq Value=Load>\n");
	printf("<input type=submit name=savefaq Value=Save>\n");
	printf("&nbsp; New FAQ: <input type=text size=15 name=newfaq>\n");
	printf("<input type=submit name=createfaq Value=Create>\n");
	printf("<br>");
	free_list(faq_list);


	printf("<input type=submit name=send Value=Send>\n");
	printf("<input type=submit name=doquote Value=Quote>\n");
	printf("&nbsp; Quote lines: <input type=text size=5 name=numquotelines Value=");
	if (numquotelines)	printf("%d>\n", numquotelines);	
	else printf("\"\">\n");
	printf("&nbsp; Add signature<input type=checkbox name=addsignature value=1 %s>\n",
	       addsignature?"CHECKED":"");

	printf("<br>");

	textarea("body", mail_y, mail_x, get_body(&info, name));

	printf("<br><input type=hidden name=compose value=\"%s\">\n", name);
	if (cgi_variable("followup"))
		printf("<input type=hidden name=followup value=\"%s\">\n", 
		       cgi_variable("followup"));

	if (faq) {
		printf("<input type=hidden name=faq value=\"%s\">\n", faq);
	}

	/* damn checkboxes - they never send a 0 answer so this is needed */
	addsignature = 0;

	hidden_globals();
	printf("</form>\n");
}

/* show the page selection links */
static void page_selection(int total)
{
	if (total <= messages_per_screen)
		return;

	printf("Select page (%d at a time): ", messages_per_screen);
	
	if (page > 0)
		printf("<A HREF=\"%s?page=%d;%s\">previous(%d)</A> \n",
		       current_page(), page-1,
		       url_globals(), page-1);

	if ((page+1)*messages_per_screen < total)
		printf("<A HREF=\"%s?page=%d;%s\">next(%d)</A> \n",
		       current_page(), page+1,
		       url_globals(), page+1);

	if (total > messages_per_screen && page > 0)
		printf("<A HREF=\"%s?page=%d;%s\">first</A> \n",
		       current_page(), 0,
		       url_globals());

	if (page < (total-1)/messages_per_screen)
		printf("<A HREF=\"%s?page=%d;%s\">last</A> \n",
		       current_page(), (total-1)/messages_per_screen,
		       url_globals());
}

/* format the notes with right number of lines */
static void format_notes(char *notes)
{
	int n = notes_lines;

	if (!notes || !*notes) {
		printf("none\n");
		return;
	}

	while (n-- && *notes) {
		char *p = strchr(notes, '\n');
		if (!p) {
			printf("%s\n", notes);
			return;
		}
		*p = 0;
		printf("%s<br>\n", notes);
		notes = p+1;
	}
	if (*notes) {
		printf("<b>...</b><br>\n");
	}
}

/* display all the mesages in a directory - this is really the guts
   of the system */
void display_dir(void)
{
	char **list;
	int i, n;
	int total, count;

	display_file("notes.html", 1);

	list = load_dir_list(".",valid_id);
	if (!list) fatal("failed to load dir");

	for (i=0;list[i];i++) ;
	total = i;

	show_directories();

	printf("<p><form method=POST action=\"%s\">\n", current_page());

	show_search_buttons();

	printf("<input type=submit name=\"dir_change\" Value=\"Submit Changes\">\n");
	printf("<input type=submit name=refresh Value=Refresh><br>\n");

	printf("<br>Notification: <input type=text size=30 name=\"dir_notification\" value=\"%s\">\n",
	       dir_notification(directory));

	printf("<p><b>%s has %d messages</b><p>\n", directory, total);
	if (*expression || selectid || messagetype)
		printf("<p><b>%d messages match search criterion</b><p>\n", 
		       (total=count_messages()));

	if (page*messages_per_screen >= total) {
		page = (total-1)/messages_per_screen;
	}


	page_selection(total);

	printf("<p>"
"<table width=\"100%%\" border=1>"
"<tr bgcolor=\"#00A020\"><th><font color=\"#FFFF00\">Id</font></th><th><font color=\"#FFFF00\">Summary</font></th><th><font color=\"#FFFF00\">Notes</font></th><th><font color=\"#FFFF00\">Move To</font></th>"
"</tr>"
"\n");

	for (count=i=0;list[i];i++) {
		struct message_info info = zero_info;
		char *notes;
		if (!valid_id(list[i])) continue;
		if (!match_message(list[i],&info)) continue;
		count++;
		if (count <= page*messages_per_screen || 
		    count > (page+1)*messages_per_screen)
			continue;		
		if (!get_info(list[i], &info, max_message_size)) continue;
		
		printf("<tr valign=top><td><A HREF=\"%s?id=%s;%s\">%s</A>\n",
		       current_page(), list[i], 
		       url_globals(),
		       list[i]);
		       
		n = count_replies(list[i], NULL);
		if (n) {
			printf("<br>%d replies\n", n);
		} 
		n = count_followups(list[i], NULL);
		if (n) {
			printf("<br>%d followups\n", n);
		} 
#if MESSAGE_LINKS
		n = count_links(list[i]);
		if (n) {
			printf("<br>%d links\n",n);
		}
#endif
		printf("</td>\n");

		printf("<td>");
		printf("<b>%s</b><br>%s<br>%s\n",
		       info.subject, info.from, info.date);
		display_pulldowns_fixed(list[i]);
		printf("</td>");
		notes = quotedup(load_ext(list[i], "notes"));
		printf("<td>");
		format_notes(notes);
		if (info.private)
			printf("<br><b>PRIVATE</b></td>");
		else
			printf("</td>");
		free(notes);
		printf("<td>\n");
		html_select_list(main_dir_list, xsprintf("moveto.%s", list[i]), directory);
		printf("<br>\n");
		printf("</td></tr>\n");
		free_info(&info);
	}

	printf("</table><p>\n");

	printf("<input type=submit name=\"dir_change\" Value=\"Submit Changes\">\n");
	printf("<input type=submit name=refresh Value=Refresh><br>\n");

	page_selection(total);

	hidden_globals();
	printf("</form>\n");

	show_directories();
	free_list(list);
}


/* login to the system */
void login_page(void)
{
	char *p;

	if (!user || !*user)
		printf("Welcome to JitterBug version %s<p>\n",VERSION);

	html_form_start(current_page());

	printf("Select your username:\n");
	html_select_list(user_list, "user", user);
	if (!*user) {
		html_input_hidden("enter", "enter");
	}
	html_input_submit("enter", "Enter System");
	html_input_submit("preferences", "Edit Preferences");
	html_input_submit("sysaudit", "View System Log");
	printf("<br>\n");

	if  ((p=cgi_variable("user")) && !valid_user(p) && 
	     !cgi_variable("newuser"))
		printf("<b>Must enter a valid username!</b>\n");

	printf("<p>Or create a new user: \n");
	html_input_text("newuser", 15);
	html_input_submit("createuser", "Create User");

	printf("<br>\n");

	if  ((p=cgi_variable("newuser")) && (*p) && !valid_user(p))
		printf("<b>new username not valid!</b>\n");

	hidden_globals();
	html_form_end();
}


/* find a specified message ID and display it */
static void find_id(void)
{
	struct message_info info = zero_info;
	char *name = cgi_variable("findid");

	if (!valid_id(name)) fatal("invalid message id \"%s\"\n", name);

	if ((directory = find_file(".",name)) == NULL) {
		fatal("error finding message %s", name);
	} else {
		char *p;

		if (strncmp(directory,"./",2) == 0) {
			directory += 2;
		}

		p = strrchr(directory,'/');
		if (!p) fatal("invalid directory in find_id\n");

		*p = '\0';

		if (chdir(directory)) fatal("can't change directory");

		directoryq = urlquote(directory);

		if (!get_info(name, &info, 0)) fatal("can't get info in find_id");

		if (guest && info.private) {
			print_title("%s - Message %s is marked PRIVATE", lp_title(), name);
			printf("<h2>Message %s is marked PRIVATE\n", name);
		} else {
			print_title("%s - Message %s", lp_title(), name);
			view_message(name);
		}
	}
}


/* display the main page which allows users to select what directory 
   to view */
void main_page(void)
{
	int i;

	html_form_start(current_page());

	if (guest) 
		display_file("/guestintro.html", 1);
	else
		display_file("/intro.html", 1);

	show_search_buttons();

	printf("<p>To view messages select a directory from the list below: \n");
	html_input_submit("refresh", "Refresh");
	printf("<br>\n");

	printf("<table border=0>\n");
	for (i=0;main_dir_list[i];i++) {
		chdir(main_dir_list[i]);
		printf("%s<td>&nbsp;<A HREF=\"%s/%s?%s\">%s</A></td><td>%d</td>%s\n",
		       (i%dir_cols)==0?"<tr>":"",
		       getenv_null("SCRIPT_NAME"),
		       urlquote(main_dir_list[i]), 
		       url_globals(),
		       main_dir_list[i],
		       count_messages(),
		       (i%dir_cols)==(dir_cols-1)?"</tr>":"");
		chdir("..");
	}
	if (i%dir_cols) {
		printf("</tr>");
	}
	printf("</table><br>\n");

	if (!guest) {
		printf("<p>Or create a new directory:<br>\n");

		html_input_text("newdir", 25);
		html_input_submit("createdir", "Create");
		printf("<p><hr>");
	}

	hidden_globals();
	html_form_end();

	if (!guest) {
		login_page();
	}
}



/* allow the user to download a file */
static void file_download(char *fname)
{
	FILE *f;
	int len;
	char buf[1000];

	if (guest && !lp_guest_download()) {
		fatal("guest download has been disabled\n");
	}

	if (strcmp(fname+strlen(fname)-strlen(download_extension()),
		   download_extension()))
		fatal("illegal filename");

	fname[strlen(fname)-strlen(download_extension())] = 0;

	if (fname[0] == '/') fatal("illegal filename %s\n", fname);

	if (strstr(fname,"..")) fatal("illegal filename %s\n", fname);

	f = fopen(fname,"r");

	if (!f) {
		fatal("unable to open file");
	}

	printf("Content-Type: application/octet-stream\n");
	if (gzip_encoding || lp_gzip_download()) {
		printf("\n");
		cgi_start_gzip();
	} else {
		printf("\n");
	}

	fflush(stdout);

	while ((len=fread(buf, 1, sizeof(buf), f)) > 0) {
		write(1, buf, len);
	}
	
	fclose(f);
}


/* handle a submitted changes form, updating the notes for a message */
static void do_msg_changes(void)
{
	char *id = cgi_variable("changeid");
	char *notes = cgi_variable("notes");
	char *privates = cgi_variable("private");
	char *notification = cgi_variable("msg_notification");
	char *orig_notes;
	int private;
	char fname[100];
	struct message_info info = zero_info;

	if (guest) {
		not_as_guest("do_msg_changes");
		return;
	}

	lock_system();

	if (!id || !valid_id(id) || !get_info(id,&info,max_message_size)) 
		return;

	orig_notes = load_ext(id, "notes");

	if (notes) {
		wrap(notes, WRAP_COLS);

		if (!orig_notes || strcmp(notes, orig_notes)) {
			check_overflow(strlen(id)+10, sizeof(fname));
			sprintf(fname,"%s.notes", id);
			if (strlen(notes) == 0) {
				unlink(fname);
			} else {
				save_file(fname,notes);
			}
			
			if (orig_notes || strlen(notes) > 0) {
				add_audit(id, "changed notes");
				notify_msg(id,"%s changed notes\n", 
					   user);
				notify_dir(directory, id, "%s changed notes\n", 
					   user);
			}
		}
	}

	if (privates) {
		private = atoi(privates);
		if (private != info.private) {
			check_overflow(strlen(id)+20, sizeof(fname));
			sprintf(fname,"%s.private", id);
			if (private) {
				int fd = open(fname,O_WRONLY|O_CREAT|O_TRUNC, 0444);
				if (fd == -1) fatal("can't create private file");
				close(fd);
			} else {
				unlink(fname);
			}
			add_audit(id, "marked %s", private?"private":"public");
		}
	}

	if (notification && strcmp(notification, msg_notification(id))) {
		check_overflow(strlen(fname)+10, sizeof(fname));
		sprintf(fname,"%s.notify", id);
		save_file(fname, notification);
		add_audit(id, "changed notification");
	}

	pulldowns_do_changes(id);

	unlock_system();
}


/* handle all submitted movement requests and changes ot the directory
   notification */
static void do_dir_changes(void)
{
	int i, j;
	char *v, *dest, *src, *p;
	char dest2[100];
	char src2[100];

	if (guest) {
		not_as_guest("do_dir_changes");
		return;
	}

	lock_system();

	if ((p = cgi_variable("dir_notification")) && 
	    strcmp(dir_notification(directory), p)) {
		/* change to directory notification */
		save_file(".notify", p);
		add_audit(SYSTEMFILE, "changed notification on %s", directory);
	}

	for (i=0;(dest = cgi_vnum(i, &v)); i++) {
		struct message_info info = zero_info;
		if (strcmp(dest,directory) == 0) continue;
		if (strncmp(v, "moveto.", 7)) continue;
		src = v+7;

		if (!valid_id(src) || !get_info(src,&info,max_message_size)) 
			continue;

		add_audit(src, "moved from %s to %s", directory, dest);
		notify_dir(dest, src, "%s moved %s%s from %s to %s", 
			   user, lp_pr_identifier() ,src, directory, dest);
		notify_dir(directory, src, "%s moved %s%s from %s to %s", 
			   user, lp_pr_identifier(), src, directory, dest);
		notify_msg(src, "%s moved %s%s from %s to %s", 
			   user, lp_pr_identifier(), src, directory, dest);

		check_overflow(sizeof(dest)+sizeof(src)+30, sizeof(dest2));

		sprintf(dest2, "../%s/%s", dest, src);
		rename(src, dest2);

		sprintf(dest2, "../%s/%s.notes", dest, src);
		sprintf(src2, "%s.notes", src);
		rename(src2, dest2);

		sprintf(dest2, "../%s/%s.audit", dest, src);
		sprintf(src2, "%s.audit", src);
		rename(src2, dest2);

		sprintf(dest2, "../%s/%s.private", dest, src);
		sprintf(src2, "%s.private", src);
		rename(src2, dest2);

		sprintf(dest2, "../%s/%s.notify", dest, src);
		sprintf(src2, "%s.notify", src);
		rename(src2, dest2);

		for (j=1;j<MAX_REPLIES;j++) {
			sprintf(dest2, "../%s/%s.reply.%d", dest, src, j);
			sprintf(src2, "%s.reply.%d", src, j);
			if (rename(src2, dest2)) break;
		}

		for (j=1;j<MAX_REPLIES;j++) {
			sprintf(dest2, "../%s/%s.followup.%d", dest, src, j);
			sprintf(src2, "%s.followup.%d", src, j);
			if (rename(src2, dest2)) break;
		}
		
	}

	unlock_system();
}


static void load_users(void)
{
	char *p;
	FILE *f;

	if (guest) {
		static char *list[] = {"guest", NULL};
		user_list = list;
		return;
	}

	user_list = load_file_list("users",valid_user);

	if (cgi_variable("createuser") &&
	    (p=cgi_variable("newuser")) && valid_user(p)) {
		if (in_list(user_list, p)) return;

		f = fopen("users", "a");
		if (f) {
			fprintf(f,"%s\n", p);
			fclose(f);
			add_audit(SYSTEMFILE,"created user %s", p);
			user_list = load_file_list("users",valid_user);
		} else {
			fatal("unable to open \"users\" for append\n");
		}
	}
}


static void set_buffering(void)
{
	char *buf = (char *)malloc(BUFSIZ + 1024);
	if (buf) {
		setbuf(stdout, buf);
	}
}


int main(int argc, char *argv[])
{
	char *s;

	load_config(argv[0]);

	/* we must do this before reading anything from the client! */
	do_chroot();

	/* notes files etc need to be available to guest users */
	umask(022);

	set_buffering();

	cgi_load_variables(NULL, NULL);

	load_globals();

	load_users(); 

	load_preferences();

	/* make sure that we die if we start looping for some
	   reason - max 5 minute run */
	alarm(300);


#ifdef DEBUG_COMMENTS
	dump_globals();
#endif

	if (cgi_variable("enter") || !cgi_variable("user")) {
		add_audit(SYSTEMFILE, "connected from %s (%s)",
			  getenv_null("REMOTE_HOST"), getenv_null("REMOTE_ADDR"));
	}

#if 0
	if (user && strcmp(user,"tridge") == 0) {
		mallopt(M_DEBUG, 1);
	}
#endif

	/* if they asked for a new directory then create it now */
	if (!guest && 
	    cgi_variable("createdir") && 
	    (s=cgi_variable("newdir")) && *s) {
		trim_string(s," "," ");
		if (mkdir(s, 0755) == 0) {
			add_audit(SYSTEMFILE, "created directory %s",s);
		} else {
			fatal("error creating directory");
		}
	}

	main_dir_list = load_dir_list(".",is_directory);
	if (!main_dir_list) fatal("failed to load dir");

	if (cgi_variable("commit") && *user && !guest) {
		save_preferences();
	}

	if (cgi_variable("fullmessage")) {
		max_message_size = 0;
	}

	if (!valid_user(user)) {
		print_title("%s - login",lp_title());
		login_page();
	} else if (cgi_variable("download")) {
		file_download(cgi_variable("download"));
		return 0;
	} else if (cgi_variable("sysaudit")) {
		print_title("%s - system audit trail",lp_title());
		view_system_audit();
	} else if ((!guest && !*fullname) || cgi_variable("preferences")) {
		print_title("%s - user preferences", lp_title());
		edit_preferences();
	} else if (cgi_variable("findid")) {
		find_id();
	} else if (directory && *directory) {
		if (cgi_variable("send") && cgi_variable("id")) {
			print_title("%s - %s/%s", lp_title(),
				    directory, cgi_variable("id"));
			if (chdir(directory)) fatal("can't change directory");
			send_reply();
			view_message(cgi_variable("id"));
		} else if (cgi_variable("compose")) {
			print_title("%s - reply %s/%s", lp_title(),
				    directory, cgi_variable("compose"));
			if (chdir(directory)) fatal("can't change directory");
			compose_reply();
		} else if (cgi_variable("msg_change")) {
			print_title("%s - %s",lp_title(), directory);
			if (chdir(directory)) fatal("can't change directory");
			do_msg_changes();
			do_dir_changes();
			display_dir();
		} else if (cgi_variable("dir_change")) {
			print_title("%s - %s", lp_title(), directory);
			if (chdir(directory)) fatal("can't change directory");
			do_dir_changes();
			display_dir();		
		} else if (cgi_variable("audit")) {
			print_title("%s - audit trail %s/%s", lp_title(), 
				    directory, cgi_variable("auditid"));
			if (chdir(directory)) fatal("can't change directory");
			view_audit();
		} else if (cgi_variable("resend")) {
			print_title("%s - %s", lp_title(), directory);
			if (chdir(directory)) fatal("can't change directory");
			resend_reply();
			display_dir();
		} else if (cgi_variable("id")) {
			print_title("%s - %s/%s", lp_title(),
				    directory, cgi_variable("id"));
			if (chdir(directory)) fatal("can't change directory");
			view_message(cgi_variable("id"));
#if MESSAGE_LINKS
		} else if (cgi_variable("updatelinks")) {
			print_title("%s - links for %s/%s", lp_title(),
				    directory, cgi_variable("links"));
			if (chdir(directory)) fatal("can't change directory");
			do_links();
			display_links(cgi_variable("links"));
		} else if (cgi_variable("links")) {
			print_title("%s - links for %s/%s", lp_title(),
				    directory, cgi_variable("links"));
			if (chdir(directory)) fatal("can't change directory");
			display_links(cgi_variable("links"));
#endif
		} else {
			print_title("%s - %s", lp_title(), directory);
			if (chdir(directory)) fatal("can't change directory");
			display_dir();		
		}
	} else if (cgi_variable("newbug")) {
		print_title("%s - bug submission", lp_title());

		compose_bug_report();
	} else {
		print_title("%s - main page", lp_title());

		if (cgi_variable("sendbugreport")) {
		  send_bug_report();
		}
		main_page();
	}

	print_footer();

	/* just in case we left it locked */
	unlock_system();

	free_list(main_dir_list);

	fflush(stdout);

	return 0;
}


