/* 
   Unix SMB/Netbios implementation.
   Version 2.0.
   status reporting in HTML
   Copyright (C) Andrew Tridgell 1994-1998

   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.

   Revision History:

   12-1998 -- Andy Bakun (thwartedefforts@wonky.org)
   Heavily based off the command line smbstatus program, with 
   some hints taken from swat. I originally wrote this as pure CGI 
   perl using Mysql to do data joining and sorting, but that was
   overkill.  Also, that would have taken too much setup for
   an out-of-the-box web reporting solution.

 */

/*
 * This program reports current SMB connections in HTML
 */

/*
 * goals:
 * - be self contained
 * - be no more intrusive into the operation of samba than 
 *   smbstatus
 * - we are one shot (do the work and exit) so being speed and space 
 *   conscience is not top priority; usablity is
 *
 * open issues (keep in mind the goals):
 *
 * - error states are not handled well. there are places where 
 *   empty web pages are generated in the face of errors.
 *
 * - (dont) it can be the case that multiple unique sessions exist 
 *   where the uid and gid are different, but when finding the uid
 *   for a locked file, for example, we only find the first
 *   one, rather than the uid who originally asked for the file.
 *   this manifests it self with force user/force group share
 *   parameters.  The way around this is to use the resouces list
 *   to map the path of the locked file to a share, then search
 *   through the sessions for the matching share.  Or, the user
 *   information could be stored in the locked file structure,
 *   but I don't want to mess with that.
 *
 * - all the duplicated code for the sort functions
 * 
 * - strncpy's all over the place where char* assignments would do
 *   don't need to duplicate the strings as much as they are.
 *   also, I should be using [pf]string rather than char arrays.
 *   silly me.
 *
 * - locked filenames are truncated at 1024 characters. either 
 *   their entire contents should be included, or they should
 *   be truncated from the middle (so you get the start of the
 *   path and the filename).  I suspose we could just use the
 *   value passed from the _forall function, but I fear 
 *   overwriting that accidentally -- a copy makes me feel
 *   better because I don't know if that fname received points
 *   to shared memory or not
 *
 * - the IP addresses are stored in struct in_addr format cast
 *   to longs, then used as integers for sorting compares.
 *   this works like a dream, but could be non-portable, and 
 *   could flop in the face of IPv6.
 *
 * - getting the process size is only implemented for Linux
 *   and then will only work if you have procfs enabled.
 *   For systems that are non-linux, the process size column
 *   doesn't show up.  For linux without procfs, it'll show up
 *   but all the sizes will be -1.  The method I use the get
 *   the process size is throughly kludgy, but I know of no
 *   syscall you can use to get process information for a process
 *   that isn't yourself or one of your child processes.
 *
 * - all over the place, I'm passing variables around in function
 *   arguments, but I don't need to because I end up using the
 *   globals, or I'm passing the global -- this is wasteful.
 *   I was unable to come up with an elegant method of not
 *   using globals.
 *
 * - sometimes, smbd processes run out of control, eating up
 *   CPU and/or memory even though they are not connected to
 *   a client.  support for listing all smbd processes should
 *   be added.
 *
 * - I know someone is going to request to be able to
 *   disconnect a client/kill a process, but I think that
 *   puts to much power in this interface, and you should do
 *   that from a shell.
 *
 * - the inline gif image storage is cool, but most likely
 *   unnecessary.  it's nice to keep things self contained though.
 *
 * - Rather than store the locked file data in it's native
 *   format (bitops), I extract that and use an entire char
 *   as a flag for the value... wasteful, but easier to 
 *   manipulate.  I should also be using the provided for
 *   macros, huh?
 */

#define SMBWEBSTATUS_VERSION "smbwebstatus-0.8.7"

#define NO_SYSLOG

#define TABLEHEADERCOLOR "#dddddd"
#define TABLESORTBYCOLOR "#888888"
#define LINKCOLOR        "#0000FF"	/* LINKCOLOR and VLINKCOLOR should be the same */
#define VLINKCOLOR       "#0000FF"	/* because the form links are in the URLs, and are not POSTs */
#define TABLEOPTS        " border=1 width=100%% cellpadding=1 cellspacing=0 "

#include "includes.h"

#ifdef LINUX
#define CAN_GET_PROCESS_SIZE 1
#endif

#define SMBWS_STR_LEN 24

#define AUPGIFLEN 66
char arrow_up_gif[AUPGIFLEN] =
{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0d, 0x00, 0x09, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x07, 0x34, 0xba, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21, 0xf9, 0x04, 0x01, 0x0a, 0x00, 0x02,
 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x40, 0x02, 0x13, 0x94, 0x8f, 0xa9,
 0x6b, 0x10, 0x0f, 0x58, 0x63, 0xa0, 0x5a, 0x2b, 0xb3, 0x70, 0x71, 0xb9, 0x07, 0x69, 0x49, 0x01,
 0x00, 0x3b};

#define ADNGIFLEN 80
char arrow_down_gif[ADNGIFLEN] =
{0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x0d, 0x00, 0x09, 0x00, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x00,
 0x07, 0x34, 0xba, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x21, 0xfe, 0x09, 0x64, 0x6f, 0x77, 0x6e,
 0x61, 0x72, 0x72, 0x6f, 0x77, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x0a, 0x00, 0x02, 0x00, 0x2c, 0x00,
 0x00, 0x00, 0x00, 0x0d, 0x00, 0x09, 0x00, 0x40, 0x02, 0x13, 0x94, 0x8f, 0xa9, 0x6b, 0x10, 0x0f,
 0x14, 0x98, 0x94, 0xb2, 0xc8, 0x32, 0x72, 0x0f, 0xde, 0x80, 0x69, 0x4a, 0x01, 0x00, 0x3b, 0x00};

struct crec_element {
    int pid;
    int cnum;
    int process_size;
    uid_t uid;
    gid_t gid;
    char service[SMBWS_STR_LEN];
    /* char addr[SMBWS_STR_LEN]; */
    char machine[SMBWS_STR_LEN];
    unsigned long addr;
    /* char addr[SMBWS_STR_LEN]; */
    time_t start;
    char username[SMBWS_STR_LEN];
    char groupname[SMBWS_STR_LEN];
    int session_count;
    struct crec_element *next;
};

struct lockedfile {
    int pid;
    char denymode;
    char rw;
    char oplock;
    time_t locktime;
    pstring filename;
    uid_t uid;
    char *username;
    gid_t gid;
    char *groupname;
    char *machine;
    unsigned long addr;
    char *service;
};
struct lockedfile **lockedfile_array = NULL;
int lockedfile_array_size = 0;
int num_lockedfiles = 0;

struct defined_service {
    char service[SMBWS_STR_LEN];
    pstring path;
    char description[SMBWS_STR_LEN];
    pstring comment;
    int open;
    int locks;
};
struct defined_service **defined_services = NULL;
int num_defined_services = 0;
int defined_services_size = 0;
BOOL found_ds_file = False;

typedef int (*compare_function) (const void *, const void *);	/* for the sorting routines */

struct crec_element **crec_ptr_array = NULL;
int crec_list_len = 0;

struct session_record {
    int pid;
    int uid;
    char machine[31];
    time_t start;
    struct session_record *next;
} *srecs;

extern int DEBUGLEVEL;
extern FILE *dbf;
extern pstring myhostname;

/* we need these because we link to locking*.o */
void become_root(BOOL save_dir)
{
}

void unbecome_root(BOOL restore_dir)
{
}

char *
 cgi_escape(char *string)
{
    /* I was hoping there was a cgi_escape function to go */
    /* along with cgi_unescape */
    string_sub(string, "%", "%25");	/* note that the % has to come first */
    string_sub(string, " ", "%20");
    string_sub(string, "\"", "%22");
    string_sub(string, "#", "%23");
    string_sub(string, "&", "%26");
    string_sub(string, "+", "%2B");
    string_sub(string, "=", "%3D");
    string_sub(string, "?", "%3F");
    return string;
}


void send_gif_image(char gif[], int len)
{
    int x;

    printf("Content-type: image/gif\r\n\r\n");
    for (x = 0; x < len; x++) {
	printf("%c", gif[x]);
    }
}

#define error_exit(code) do_error_exit(code, __LINE__)
void do_error_exit(int code, int line)
{
    switch (code) {
    case 3:
	fprintf(stderr, "out of memory in line %d\n", line);
	break;
    case 4:
	fprintf(stderr, "list empty\n");
	break;
    default:
	fprintf(stderr, "exiting with unknown error\n");
	code = 1;
    }
    exit(code);
}

/* returns true if lookfor is already in array as a uniq session
 * uniqueness of a session is determined by all fields except the service name and start time
 */
struct crec_element *
 session_exists_in_array(struct crec_element *array[], int len, const struct crec_element *lookfor)
{
    int x;

    if (!len)
	return 0;

    for (x = 0; x < len; x++) {
	if ((array[x]->pid == lookfor->pid)
	    && (array[x]->uid == lookfor->uid)
	    && (array[x]->gid == lookfor->gid)
	    && (strncmp(array[x]->machine, lookfor->machine, SMBWS_STR_LEN) == 0)
	    && (array[x]->addr == lookfor->addr)
	    )
	    return array[x];
    }
    return NULL;

}

/* this function looks up the service name in the share list
 * then uses that along with the pid to determine the session
 * that contains this locked file
 */
struct crec_element *
 find_lockedfile_session(int pid, char *fname)
{
    int x, y;
    BOOL found = 0;

    if (found_ds_file) {
	/* use a more reliable method to find the session 
	 * first, find the defined share that matches the path
	 * of the locked file, then find the process that has
	 * the share open with that name
	 */
	struct defined_service *ds;
	for (x = 0; !found && x < num_defined_services; x++) {
	    ds = defined_services[x];
	    if (strncmp(ds->path, fname, strlen(ds->path)) == 0) {
		for (y = 0; !found && y < crec_list_len; y++) {
		    if (crec_ptr_array[y]->pid == pid && strncmp(crec_ptr_array[y]->service, ds->service, SMBWS_STR_LEN) == 0) {
			found = 1;
			break;
		    }
		}
	    }
	}
    } else {
	/* use the less reliable method... the first one found, make a guess at the username
	 * since we don't know about the defined service, we'll end up only using the username 
	 * which has a chance of being wrong anyway (because we return the first we find)
	 */
	for (y = 0; !found && y < crec_list_len; y++) {
	    if (crec_ptr_array[y]->pid == pid) {
		found = 1;
		break;
	    }
	}
    }

    if (!found)
	return NULL;

    return crec_ptr_array[y];
}

/* this is a very linux specific function, I know of no other way to determine a 
 * non-child process's size other than reading out of proc -- there has to be a more
 * portable way
 * returns number of bytes of process size, -1 for unknown 
 * always returns -1 for non-Linux
 */
int get_process_size(int pid)
{
    int size = -1;

#ifdef CAN_GET_PROCESS_SIZE
    FILE *sfile;
    char f[128];

    snprintf(f, 128, "/proc/%d/status", pid);
    sfile = fopen(f, "r");
    if (!sfile)
	return -1;
    while (!feof(sfile)) {
	fgets(f, 128, sfile);
	if (strncmp(f, "VmSize:", 7) == 0) {
	    sscanf(&f[9], "%d", &size);
	    size *= 1024;
	    break;
	}
    }
    fclose(sfile);
#endif

    return size;
}

/* returns in dest the unique session list
 * the return value is the number of entries in dest 
 * dest should be precreated to at least be len items long
 */
int get_unique_session_listing( /* struct crec_element *array[], int len, */ struct crec_element *dest[])
{
    int x;
    int destlen = 0;
    struct crec_element *found;

    for (x = 0; x < crec_list_len; x++) {
	found = session_exists_in_array(dest, destlen, crec_ptr_array[x]);
	if (!found) {
	    dest[destlen] = crec_ptr_array[x];
	    dest[destlen]->session_count = 1;
	    dest[destlen]->process_size = get_process_size(crec_ptr_array[x]->pid);
	    destlen++;
	} else
	    found->session_count++;
    }
    return destlen;
}

/*
 * returns, in dest, the locked files that are within path
 * dest should be at least num_lockedfiles long
 */
int get_locked_files_in_path(char *path, struct lockedfile *dest[])
{
    int x;
    int pathlen;
    int destlen = 0;

    if (!path)
	return 0;
    pathlen = strlen(path);

    for (x = 0; x < num_lockedfiles; x++) {
	if (strncmp(lockedfile_array[x]->filename, path, pathlen) == 0)
	    dest[destlen++] = lockedfile_array[x];
    }
    return destlen;
}

/* all this function does right now is remove the
 * day of week from a ctime() call.. I'd like to get 
 * it to return a more compact date/time string, but
 * that's in the next release :)
 */
char *
 format_datetime(int t)
{
    static char f[50];

    snprintf(f, 50, "%s", ctime((const time_t *) &t));
    return &f[4];
}

void print_document_header(void)
{
    char *mode = cgi_variable("mode");
    char *x;
    pstring otheropts;

#define HILIGHT(st, c)  (mode[0] == (c) ? (st) : "")

    x = cgi_variable("sortby");
    otheropts[0] = 0;
    if (x && x[0])
	snprintf(otheropts, 1024, "&sortby=%s", x);
    x = cgi_variable("exidle");
    if (x && x[0]) {
        fstring temp;
	fstrcpy(temp, otheropts);
	snprintf(otheropts, 1024, "%s&exidle=%s", temp, x);
    }
    printf("<table width=100%% border=0>\n");
    printf("<TR bgcolor=%s><TH width=20%% %s>\n", TABLEHEADERCOLOR, mode[0] == 's' ? "bgcolor=" TABLESORTBYCOLOR : "");
    printf("<a href=\"?mode=sessions%s\" target=top>Sessions</a>", HILIGHT(otheropts, 's'));
    if (found_ds_file)
	printf("</TH><TH width=20%% %s><a href=\"?mode=resources%s\" target=top>Resources</a>", (mode[0] == 'r' ? "bgcolor=" TABLESORTBYCOLOR : ""), HILIGHT(otheropts, 'r'));
    printf("</TH><TH width=20%% %s><a href=\"?mode=lockedfiles%s\" target=top>Locked&nbsp;Files</a>\n", (mode[0] == 'l' ? "bgcolor=" TABLESORTBYCOLOR : ""), HILIGHT(otheropts, 'l'));
    printf("</TH><TD align=right><font size=-2>%s</font>", SMBWEBSTATUS_VERSION);
    printf("</TD></TR>\n");
    printf("</table>\n");
}


int sort_definedservices_by_pathlen(const void *av, const void *bv)
{
    struct defined_service **a = (struct defined_service **) av;
    struct defined_service **b = (struct defined_service **) bv;
    int alen = strlen((*a)->path);
    int blen = strlen((*b)->path);

    /* sort with the longer names first */
    if (blen < alen)
	return -1;
    if (alen == blen)
	return 0;
    return 1;
}


/* These functions are a space/speed tradeoff.  I could have made one function 
 * that looked at a global variable to determine what to sort by, but that would
 * have meant more overhead on each iteration.  Sure, we essentially duplicate
 * the function, but this saves us some type casting issues.
 *
 * another way to do it would be to have the global variable hold the offset
 * into the structure of what to compare -- then we'd just need functions for
 * each type (although string and int are the only types right now).  This 
 * brings up a portablity issue... which is more stable? the ability of the
 * compiler to generate the correct offsets of structure members and then
 * doing pointer math, or the ## macros I use to generate these. Both should
 * be standard ANSI C.
 *
 * besides, I like macros that create functions
 */

#define MKSORTFUNC_ASC_INT(label,by,type) int sort_ ## label ## _by_ ## by ## _asc(const void *av, const void *bv) { \
                                     type **a = (type **) av; \
			             type **b = (type **) bv; \
                                     if ((*a)-> ## by < (*b)-> ## by ) return -1; \
			             if ((*a)-> ## by == (*b)-> ## by ) return 0; \
				     return 1; \
			           }

#define MKSORTFUNC_DES_INT(label,by,type) \
                                   int sort_ ## label ## _by_ ## by ## _des(const void *av, const void *bv) { \
                                     return - (sort_ ## label ## _by_ ## by ## _asc(av, bv)); \
			           }

#define MKSORTFUNC_ASC_STR(label,by,type) \
                                   int sort_ ## label ## _by_ ## by ## _asc(const void *av, const void *bv) { \
                                     type **a = (type **) av; \
			             type **b = (type **) bv; \
				     return strcasecmp((*a)-> ## by, (*b)-> ## by); \
			           }

#define MKSORTFUNC_DES_STR(label,by,type) \
                                   int sort_ ## label ## _by_ ## by ## _des(const void *av, const void *bv) { \
                                     return - (sort_ ## label ## _by_ ## by ## _asc(av, bv)); \
			           }

/* sort a crec_element array */
MKSORTFUNC_ASC_INT(crec, pid, struct crec_element);
MKSORTFUNC_DES_INT(crec, pid, struct crec_element);
MKSORTFUNC_ASC_INT(crec, uid, struct crec_element);
MKSORTFUNC_DES_INT(crec, uid, struct crec_element);
MKSORTFUNC_ASC_INT(crec, gid, struct crec_element);
MKSORTFUNC_DES_INT(crec, gid, struct crec_element);
MKSORTFUNC_ASC_INT(crec, start, struct crec_element);
MKSORTFUNC_DES_INT(crec, start, struct crec_element);
MKSORTFUNC_ASC_STR(crec, service, struct crec_element);
MKSORTFUNC_DES_STR(crec, service, struct crec_element);
MKSORTFUNC_ASC_STR(crec, username, struct crec_element);
MKSORTFUNC_DES_STR(crec, username, struct crec_element);
MKSORTFUNC_ASC_STR(crec, groupname, struct crec_element);
MKSORTFUNC_DES_STR(crec, groupname, struct crec_element);
MKSORTFUNC_ASC_STR(crec, machine, struct crec_element);
MKSORTFUNC_DES_STR(crec, machine, struct crec_element);
MKSORTFUNC_ASC_INT(crec, addr, struct crec_element);
MKSORTFUNC_DES_INT(crec, addr, struct crec_element);
MKSORTFUNC_ASC_INT(crec, process_size, struct crec_element);
MKSORTFUNC_DES_INT(crec, process_size, struct crec_element);
MKSORTFUNC_ASC_INT(crec, session_count, struct crec_element) MKSORTFUNC_DES_INT(crec, session_count, struct crec_element)
/* sort a lockedfile array */
 MKSORTFUNC_ASC_INT(lockedfile, pid, struct lockedfile);
MKSORTFUNC_DES_INT(lockedfile, pid, struct lockedfile);
MKSORTFUNC_ASC_INT(lockedfile, uid, struct lockedfile);
MKSORTFUNC_DES_INT(lockedfile, uid, struct lockedfile);
MKSORTFUNC_ASC_INT(lockedfile, locktime, struct lockedfile);
MKSORTFUNC_DES_INT(lockedfile, locktime, struct lockedfile);
MKSORTFUNC_ASC_STR(lockedfile, filename, struct lockedfile);
MKSORTFUNC_DES_STR(lockedfile, filename, struct lockedfile);
MKSORTFUNC_ASC_STR(lockedfile, username, struct lockedfile);
MKSORTFUNC_DES_STR(lockedfile, username, struct lockedfile);
MKSORTFUNC_ASC_STR(lockedfile, service, struct lockedfile);
MKSORTFUNC_DES_STR(lockedfile, service, struct lockedfile);

/* sort a defined_service array */
MKSORTFUNC_ASC_STR(services, service, struct defined_service);
MKSORTFUNC_DES_STR(services, service, struct defined_service);
MKSORTFUNC_ASC_STR(services, path, struct defined_service);
MKSORTFUNC_DES_STR(services, path, struct defined_service);
MKSORTFUNC_ASC_STR(services, description, struct defined_service);
MKSORTFUNC_DES_STR(services, description, struct defined_service);
MKSORTFUNC_ASC_STR(services, comment, struct defined_service);
MKSORTFUNC_DES_STR(services, comment, struct defined_service);
MKSORTFUNC_ASC_INT(services, open, struct defined_service);
MKSORTFUNC_DES_INT(services, open, struct defined_service);
MKSORTFUNC_ASC_INT(services, locks, struct defined_service);
MKSORTFUNC_DES_INT(services, locks, struct defined_service);

#define MKSESSIONS_MATCH_INT(find) int sessions_that_match_ ## find(int lookfor, struct crec_element *array[], int len, struct crec_element *dest[]) { \
                                     int x; int destlen = 0; \
                                     for (x = 0; x < len; x++) { \
				       if (array[x]-> ## find == lookfor) \
				         dest[destlen++] = array[x]; \
					 array[x]->process_size = get_process_size(array[x]->pid); \
				     } \
				     return destlen; \
                                   }

#define MKSESSIONS_MATCH_STR(find) int sessions_that_match_ ## find(char *lookfor, struct crec_element *array[], int len, struct crec_element *dest[]) { \
                                     int x; int destlen = 0; int lookforlen = strlen(lookfor); \
                                     for (x = 0; x < len; x++) { \
				       if (strncasecmp(array[x]-> ## find, lookfor, lookforlen + 1) == 0) \
				         dest[destlen++] = array[x]; \
					 array[x]->process_size = get_process_size(array[x]->pid); \
				     } \
				     return destlen; \
                                   }
MKSESSIONS_MATCH_INT(pid);
MKSESSIONS_MATCH_INT(uid);
MKSESSIONS_MATCH_INT(gid);
MKSESSIONS_MATCH_INT(start);
MKSESSIONS_MATCH_STR(username);
MKSESSIONS_MATCH_STR(groupname);
MKSESSIONS_MATCH_STR(service);
MKSESSIONS_MATCH_STR(machine);
MKSESSIONS_MATCH_INT(addr);

char *get_sort_direction_img(char *sortedby)
{
    if (sortedby[0] == tolower(sortedby[0])) {	/* upper case first char is the flag that it's sorted in desc order */
	return "<img src=\"arrow_up.gif\" border=0 alt=\"descending\">";
    } else if (sortedby[0] == toupper(sortedby[0])) {	/* lower case first char means sorted in asc order */
	return "<img src=\"arrow_down.gif\" border=0 alt=\"ascending\">";
    }
    return "";
}

void read_crec_status_file(void)
{
    struct crec_element *crec_head;
    struct crec_element *crec_tail;
    struct connect_record current;
    pstring fname;
    FILE *f;
    int x;

    pstrcpy(fname, lp_lockdir());
    standard_sub_basic(fname);
    trim_string(fname, "", "/");
    pstrcat(fname, "/STATUS..LCK");

    f = sys_fopen(fname, "r");
    if (!f) {
	printf("Couldn't open status file %s\n", fname);
	if (!lp_status(-1))
	    printf("You need to have status=yes in your smb config file\n");
	exit(1);
    }
    crec_head = NULL;
    crec_tail = NULL;

    while (!feof(f)) {
	if (fread(&current, sizeof(current), 1, f) != 1)
	    break;
	if (current.cnum == -1)
	    continue;

	if (current.magic == 0x280267 && process_exists(current.pid)) {
	    /* add it to the list, set the current->next to point to head, set head to current */
	    crec_tail = calloc(sizeof(struct crec_element), 1);
	    if (!crec_tail)
		exit(3);
	    crec_tail->next = crec_head;

	    crec_tail->pid = current.pid;
	    crec_tail->cnum = current.cnum;
	    crec_tail->uid = current.uid;
	    crec_tail->gid = current.gid;
	    crec_tail->start = current.start;
	    strncpy(crec_tail->service, current.name, SMBWS_STR_LEN);
	    strncpy(crec_tail->machine, current.machine, SMBWS_STR_LEN);
	    crec_tail->addr = inet_addr(current.addr);
	    strncpy(crec_tail->username, uidtoname(current.uid), SMBWS_STR_LEN);
	    strncpy(crec_tail->groupname, gidtoname(current.gid), SMBWS_STR_LEN);

	    crec_head = crec_tail;
	    crec_list_len++;
	}
    }
    fclose(f);

    if (!crec_head)
	return;

    /* now, build an array of pointers, to ease sorting */
    crec_ptr_array = calloc(crec_list_len, sizeof(struct crec_element *));
    if (!crec_ptr_array)
	error_exit(3);
    for (x = 0; x < crec_list_len; x++) {
	crec_ptr_array[x] = crec_head;
	crec_head = crec_head->next;
    }
}


void do_frames()
{
    printf("<frameset rows=\"500,*\">\n");
    printf("  <frame src=\"/?mode=sessions\" name=\"top\">\n");
    printf("  <frame src=\"/?mode=empty\" name=\"bottom\">\n");
    printf("</frameset>\n");
}

void remember_lockedfile(share_mode_entry * e, char *fname)
{
    struct lockedfile *lf;
    struct crec_element *ses;

    if (lockedfile_array_size <= num_lockedfiles) {	/* make the array bigger, use Synder's rule */
	lockedfile_array_size *= 2;
	if (lockedfile_array_size < 1)
	    lockedfile_array_size = 1;
	lockedfile_array = realloc(lockedfile_array, lockedfile_array_size * sizeof(struct lockedfile *));
	if (!lockedfile_array)
	    error_exit(3);
    }
    lf = calloc(1, sizeof(struct lockedfile));
    if (!lf)
	error_exit(3);
    lockedfile_array[num_lockedfiles++] = lf;
    lf->pid = e->pid;
    switch ((e->share_mode >> 4) & 0xF) {
    case DENY_NONE:
	lf->denymode = 'N';
	break;
    case DENY_ALL:
	lf->denymode = 'A';
	break;
    case DENY_DOS:
	lf->denymode = 'D';
	break;
    case DENY_READ:
	lf->denymode = 'R';
	break;
    case DENY_WRITE:
	lf->denymode = 'W';
	break;
    }
    switch (e->share_mode & 0xF) {
    case 0:
	lf->rw = 'R';
	break;
    case 1:
	lf->rw = 'W';
	break;
    case 2:
	lf->rw = 'B';
	break;
    }
    if ((e->op_type & (EXCLUSIVE_OPLOCK | BATCH_OPLOCK)) == (EXCLUSIVE_OPLOCK | BATCH_OPLOCK))
	lf->oplock = 'A';
    else if (e->op_type & EXCLUSIVE_OPLOCK)
	lf->oplock = 'E';
    else if (e->op_type & BATCH_OPLOCK)
	lf->oplock = 'B';
    else
	lf->oplock = 'N';

    lf->locktime = e->time.tv_sec;
    strncpy(lf->filename, fname, 1024);
    /* for the case where the share name is included before the filename
       don't forget to change the assignment to service below if this code
       gets reenabled
       lf->service = lf->name;
       lf->filename = lf->service + strlen(lf->service) + 2;
     */

    if ((ses = find_lockedfile_session(lf->pid, lf->filename))) {
	lf->uid = ses->uid;
	lf->gid = ses->gid;
	lf->username = ses->username;
	lf->groupname = ses->groupname;
	lf->machine = ses->machine;
	lf->addr = ses->addr;
	lf->service = ses->service;
    } else {
	lf->uid = -1;
	lf->gid = -1;
	lf->username = NULL;
	lf->groupname = NULL;
	lf->machine = NULL;
	lf->addr = 0;
	lf->service = NULL;
    }

}

void read_locked_files(void)
{
    int cnt;

    if (!locking_init(1)) {
	printf("Can't initialise shared memory - exiting\n");
	exit(1);
    }
    /* remember_lockedfile writes into a global 
     * variable, we can't give it extra arguments 
     */
    cnt = share_mode_forall(remember_lockedfile);
    /* we just dump the return value, because 
     * num_locked_files will contain the count
     */
}

#define DETERMINEREVSORT(x)    pstrcpy(nextsort, (x)); if (strcmp(sortedby, nextsort) == 0) nextsort[0] = tolower(nextsort[0])
#define COLORFORHEADER(s,var) (strcasecmp((var), (s)) == 0 ? "bgcolor=" TABLESORTBYCOLOR : "")
#define INCLUDEARROW(s,var)   (strcasecmp((var), (s)) == 0 ? imgtag : "")

void formattable_resources(struct defined_service *array[], int len, char *sortedby)
{
    int x;
    char *sb = cgi_variable("exidle");
    char *imgtag = get_sort_direction_img(sortedby);
    pstring nextsort;
    int exidle;
    pstring otheropts;

    exidle = 0;
    if (sb)
	exidle = atoi(sb);

    sb = cgi_variable("sortby");
    otheropts[0] = 0;
    if (sb && sb[0]) {
        fstring temp;
	fstrcpy(temp, otheropts);
	snprintf(otheropts, 1024, "%s&sortby=%s", temp, sb);
    }

    printf("%d services found.\n", len);
    if (exidle) {
	printf("<a href=\"/?mode=resources%s\">Include idle services</a> (Idle services not shown)<br>\n", otheropts);
    } else {
	printf("<a href=\"/?mode=resources&exidle=1%s\">Exclude idle services</a><br>\n", otheropts);
    }

    /* do it again, but just for the sortby links */
    otheropts[0] = 0;
    if (exidle)
	snprintf(otheropts, 1024, "&exidle=1");

    printf("<center><table" TABLEOPTS ">\n");

    printf("<TR bgcolor=%s>\n", TABLEHEADERCOLOR);
    DETERMINEREVSORT("Service");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Service%s</a></TH>", COLORFORHEADER("service", sortedby), nextsort, otheropts, INCLUDEARROW("service", sortedby));
    DETERMINEREVSORT("Path");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Path%s</a></TH>", COLORFORHEADER("path", sortedby), nextsort, otheropts, INCLUDEARROW("path", sortedby));
    DETERMINEREVSORT("Open");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Open%s</a></TH>", COLORFORHEADER("open", sortedby), nextsort, otheropts, INCLUDEARROW("open", sortedby));
    DETERMINEREVSORT("Locks");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Locks%s</a></TH>", COLORFORHEADER("locks", sortedby), nextsort, otheropts, INCLUDEARROW("locks", sortedby));
    DETERMINEREVSORT("Description");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Description%s</a></TH>", COLORFORHEADER("description", sortedby), nextsort, otheropts, INCLUDEARROW("description", sortedby));
    DETERMINEREVSORT("Comment");
    printf("<TH %s><a href=\"/?mode=resources&sortby=%s%s\">Comment%s</a></TH>", COLORFORHEADER("comment", sortedby), nextsort, otheropts, INCLUDEARROW("comment", sortedby));
    printf("</TR>\n");
    for (x = 0; x < len; x++) {
	if (!exidle || (exidle && array[x]->open > 0)) {
	    printf("<TR>\n");
	    printf("<TD><tt>&nbsp;&nbsp;<a target=bottom href=\"/?mode=specsession&searchfor=%s&sortby=Username&column=service\">%s</a></tt></TD>\n", array[x]->service, array[x]->service);
	    printf("<TD><font size=-1><tt>&nbsp;%s</tt></font></TD>\n", array[x]->path);
	    printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->open);
	    if (array[x]->locks) {
	      printf("<TD align=right><tt><a href=\"/?mode=lockedlist&path=%s\" target=bottom>%d</a>&nbsp;</tt></TD>\n", cgi_escape(array[x]->path), array[x]->locks);
	    } else {
	      /* printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->locks); */
	      printf("<TD align=right><tt>0&nbsp;</tt></TD>\n"); /* we know it's zero if we got here */
	    }
	    printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->description);
	    printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->comment);
	    printf("</TR>\n");
	}
    }
    printf("</table>\n");

}

#define COMPARESORT(var, label, column, string)  \
                                         if (var == NULL && strcasecmp(c, string) == 0 && c[0] == toupper(c[0])) \
                                           var = sort_ ## label ## _by_ ## column ## _asc; \
                                         else if (var == NULL && strcasecmp(c, string) == 0 && c[0] == tolower(c[0])) \
                                           var = sort_ ## label ## _by_ ## column ## _des;

void do_resources(struct crec_element *crec_array[], int crec_array_len)
{
    char *c = cgi_variable("sortby");
    compare_function cmpfunc = NULL;

    if (c) {
	COMPARESORT(cmpfunc, services, service, "service")
	    COMPARESORT(cmpfunc, services, path, "path")
	    COMPARESORT(cmpfunc, services, open, "open")
	    COMPARESORT(cmpfunc, services, locks, "locks")
	    COMPARESORT(cmpfunc, services, description, "description")
	    COMPARESORT(cmpfunc, services, comment, "comment")
    }
    if (!cmpfunc) {
	cmpfunc = sort_services_by_service_asc;
	c = "Service";
    }
    qsort(defined_services, num_defined_services, sizeof(struct defined_services *), cmpfunc);
    print_document_header();
    formattable_resources(defined_services, num_defined_services, c);

}

void read_share_list_file(void)
{
    FILE *in;
    char buf[512];
    int eof_happened = 0;
    struct defined_service *cur;
    pstring fname;

    pstrcpy(fname, lp_lockdir());
    standard_sub_basic(fname);
    trim_string(fname, "", "/");
    pstrcat(fname, "/share_list");

    in = sys_fopen(fname, "r");
    if (!in)
	return;

    while (!eof_happened) {
	if (defined_services_size <= num_defined_services) {	/* make the array bigger, use Synder's rule */
	    defined_services_size *= 2;
	    if (defined_services_size < 1)
		defined_services_size = 1;
	    defined_services = realloc(defined_services, defined_services_size * sizeof(struct defined_services *));
	    if (!defined_services)
		error_exit(3);
	}
	if (fgets(buf, 255, in) == NULL) {
	    eof_happened = 1;
	    continue;
	}
	cur = calloc(1, sizeof(struct defined_service));
	if (!cur)
	    error_exit(3);
	defined_services[num_defined_services++] = cur;
	sscanf(buf, "%1024[^\t]\t%24[^\t]\t%24[^\t]\t%1024[^\t\n]", cur->path, cur->service, cur->description, cur->comment);
	cur->open = cur->locks = 0;
    }

    if (num_defined_services)
	found_ds_file = True;
}

void determine_open_and_locks_for_defined_services(void)
{
    int x;
    int y;

    for (x = 0; x < crec_list_len; x++) {
	for (y = 0; y < num_defined_services; y++) {
	    if (strcmp(crec_ptr_array[x]->service, defined_services[y]->service) == 0) {
		defined_services[y]->open++;
	    }
	}
    }

    qsort(defined_services, num_defined_services, sizeof(struct defined_services *), sort_definedservices_by_pathlen);

    for (x = 0; x < num_lockedfiles; x++) {
	for (y = 0; y < num_defined_services; y++) {
	    if (strncmp(lockedfile_array[x]->filename, defined_services[y]->path, strlen(defined_services[y]->path)) == 0) {
		defined_services[y]->locks++;
		break;
	    }
	}
    }

}

void formattable_lockedfiles(struct lockedfile *array[], int len, char *sortedby)
{
    int x;
    char *imgtag = get_sort_direction_img(sortedby);
    pstring fnamelink;
    pstring nextsort;
    pstring link;
    char *cmode = cgi_variable("mode");
    char *cpath = cgi_variable("path");

    link[0] = 0;
    snprintf(link, 1024, "mode=%s", cmode);
    if (cpath && cpath[0]) {
        fstring temp;
	fstrcpy(temp, link);
	pstrcpy(nextsort, cpath);	/* use nextsort as temp storage */
	snprintf(link, 1024, "%s&path=%s", temp, cgi_escape(nextsort));
	nextsort[0] = 0;
    }
    printf("%d locked files.\n", len);
    printf("<center><table" TABLEOPTS ">\n");

    printf("<TR bgcolor=%s>\n", TABLEHEADERCOLOR);
    DETERMINEREVSORT("Username");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">%sUsername%s</a></TH>", COLORFORHEADER("username", sortedby), link, nextsort, found_ds_file ? "" : "Possible<br>", INCLUDEARROW("username", sortedby));

    DETERMINEREVSORT("Uid");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">%sUid%s</a></TH>", COLORFORHEADER("uid", sortedby), link, nextsort, found_ds_file ? "" : "Possible<br>", INCLUDEARROW("uid", sortedby));

    DETERMINEREVSORT("Pid");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Pid%s</a></TH>", COLORFORHEADER("pid", sortedby), link, nextsort, INCLUDEARROW("pid", sortedby));

    if (found_ds_file) {
	DETERMINEREVSORT("Service");
	printf("<TH %s><a href=\"/?%s&sortby=%s\">Service%s</a></TH>", COLORFORHEADER("service", sortedby), link, nextsort, INCLUDEARROW("service", sortedby));
    }
    DETERMINEREVSORT("Filename");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Filename%s</a></TH>", COLORFORHEADER("filename", sortedby), link, nextsort, INCLUDEARROW("filename", sortedby));

    printf("</TR>\n");
    for (x = 0; x < len; x++) {
	printf("<TR>\n");
	printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->username);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->uid);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->pid);
	if (found_ds_file)
	    printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->service);
	printf("<TD><font size=-1>");
	pstrcpy(fnamelink, array[x]->filename);
	printf("<a target=bottom href=\"?mode=speclock&pid=%d&fname=%s\">", array[x]->pid, cgi_escape(fnamelink));
	printf("<tt>%s</tt></a></font></TD>\n", array[x]->filename);
	printf("</TR>\n");
    }
    printf("</table>\n");
}

void do_locked_files(struct crec_element *crec_array[], int crec_array_len)
{
    char *c = cgi_variable("sortby");
    compare_function cmpfunc = NULL;

    if (!num_lockedfiles) {
	printf("No locked files\n");
	return;
    }
    if (c) {
	COMPARESORT(cmpfunc, lockedfile, username, "username")
	    COMPARESORT(cmpfunc, lockedfile, uid, "uid")
	    COMPARESORT(cmpfunc, lockedfile, pid, "pid")
	    COMPARESORT(cmpfunc, lockedfile, service, "service")
	    COMPARESORT(cmpfunc, lockedfile, filename, "filename")
    }
    if (!cmpfunc) {
	cmpfunc = sort_lockedfile_by_filename_asc;
	c = "Filename";
    }
    qsort(lockedfile_array, num_lockedfiles, sizeof(struct lockedfile *), cmpfunc);
    print_document_header();
    formattable_lockedfiles(lockedfile_array, num_lockedfiles, c);

}

void do_spec_locked_file(struct crec_element *crec_array[], int crec_array_len)
{
    char *fname = cgi_variable("fname");
    int pid = atoi(cgi_variable("pid"));
    int x;
    struct lockedfile *lf;

    if (!pid || !fname) {
	printf("Error in form submission.\n");
	return;
    }
    for (x = 0; x < num_lockedfiles; x++) {
	if (lockedfile_array[x]->pid == pid && strcmp(lockedfile_array[x]->filename, fname) == 0)
	    break;
    }
    if (x == num_lockedfiles) {
	printf("File <b><tt>%s</tt></b> does not appear to be locked.<br>The lock might have been released.\n", fname);
	return;
    }
    lf = lockedfile_array[x];
    printf("<center><table border=1 width=100%% cellpadding=3 cellspacing=0>\n");

    printf("<TR><TH align=right width=10%%>Filename</TH><TD><font size=-1><tt>%s</TT></font></TD></TR>\n", lf->filename);
    if (found_ds_file)
	printf("<TR><TH align=right>Possible Share</TH><TD>%s</TD></TR>\n", lf->service);
    printf("<TR><TH align=right>%sUser</TH><TD>%s (%d)</TD></TR>\n", found_ds_file ? "" : "Possible ", lf->username, lf->uid);
    printf("<TR><TH align=right>%sGroup</TH><TD>%s (%d)</TD></TR>\n", found_ds_file ? "" : "Possible ", lf->groupname, lf->gid);
    printf("<TR><TH align=right>Process</TH><TD>%d</TD></TR>\n", lf->pid);
    printf("<TR><TH align=right>Machine</TH><TD>%s (%s)</TD></TR>\n", lf->machine, inet_ntoa(*((struct in_addr *) &lf->addr)));
    printf("<TR><TH align=right>Deny Mode</TH><TD><tt>");
    switch (lockedfile_array[x]->denymode) {
    case 'N':
	printf("DENY_NONE");
	break;
    case 'A':
	printf("DENY_ALL");
	break;
    case 'D':
	printf("DENY_DOS");
	break;
    case 'R':
	printf("DENY_READ");
	break;
    case 'W':
	printf("DENY_WRITE");
	break;
    }
    printf("</tt></TD></TR>\n<TR><TH align=right>Open Mode</TH><TD><tt>");
    switch (lockedfile_array[x]->rw) {
    case 'R':
	printf("RDONLY");
	break;
    case 'W':
	printf("WRONLY");
	break;
    case 'B':
	printf("RDWR");
	break;
    }
    printf("</tt></TD></TR>\n<TR><TH align=right>Oplock</TH><TD><tt>");
    switch (lockedfile_array[x]->oplock) {
    case 'A':
	printf("EXCLUSIVE & BATCH");
	break;
    case 'E':
	printf("EXCLUSIVE");
	break;
    case 'B':
	printf("BATCH");
	break;
    case 'N':
	printf("(none)");
	break;
    }
    printf("</tt></TD></TR>\n");

    printf("<TR><TH align=right>Lock Time</TH><TD>%s</TD></TR>", format_datetime(lf->locktime));
    printf("</table></center>\n");

}

void do_lockedfile_list()
{
    char *npath = cgi_variable("path");
    int nl;
    struct lockedfile **dest;
    char *c = cgi_variable("sortby");
    compare_function cmpfunc = NULL;
    pstring path;

    if (!num_lockedfiles) {
	printf("No locked files\n");
	return;
    }
    if (!npath || !npath[0]) {
	printf("Unknown path received\n");
	return;
    }
    dest = calloc(num_lockedfiles, sizeof(struct lockedfile *));
    if (!dest)
	error_exit(3);

    snprintf(path, 1024, "%s/", npath);
    nl = get_locked_files_in_path(path, dest);
    if (!nl) {
	printf("No locked files in %s\n", path);
	return;
    }
    if (c) {
	COMPARESORT(cmpfunc, lockedfile, username, "username")
	    COMPARESORT(cmpfunc, lockedfile, uid, "uid")
	    COMPARESORT(cmpfunc, lockedfile, pid, "pid")
	    COMPARESORT(cmpfunc, lockedfile, service, "service")
	    COMPARESORT(cmpfunc, lockedfile, filename, "filename")
    }
    if (!cmpfunc) {
	cmpfunc = sort_lockedfile_by_filename_asc;
	c = "Filename";
    }
    qsort(dest, nl, sizeof(struct lockedfile *), cmpfunc);
    formattable_lockedfiles(dest, nl, c);

}

char *
 bytestostr(int bytes)
{
    static char output[128];
#define KILO (1024)
#define MEG  (KILO * 1024)

    if (bytes < MEG) {
	snprintf(output, 128, "%4.2f kilo", ((float) bytes) / ((float) KILO));
    } else {
	snprintf(output, 128, "%4.2f meg", ((float) bytes) / ((float) MEG));
    }
    return output;
}

void formattable_sessions(struct crec_element *array[], int len, char *sortedby)
{
    int x;
    char *imgtag = get_sort_direction_img(sortedby);
    pstring nextsort;

    printf("Currently active sessions:\n");
    printf("<center><table" TABLEOPTS ">\n");
    printf("<TR bgcolor=%s>\n", TABLEHEADERCOLOR);

    DETERMINEREVSORT("Username");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">User%s</a></TH>", COLORFORHEADER("username", sortedby), nextsort, INCLUDEARROW("username", sortedby));
    DETERMINEREVSORT("Uid");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Uid%s</a></TH>", COLORFORHEADER("uid", sortedby), nextsort, INCLUDEARROW("uid", sortedby));
    DETERMINEREVSORT("Groupname");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Group%s</a></TH>", COLORFORHEADER("groupname", sortedby), nextsort, INCLUDEARROW("groupname", sortedby));
    DETERMINEREVSORT("Gid");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Gid%s</a></TH>", COLORFORHEADER("gid", sortedby), nextsort, INCLUDEARROW("gid", sortedby));
    DETERMINEREVSORT("Session_count");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Open Services%s</a></TH>", COLORFORHEADER("session_count", sortedby), nextsort, INCLUDEARROW("session_count", sortedby));
    DETERMINEREVSORT("Pid");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Process%s</a></TH>", COLORFORHEADER("pid", sortedby), nextsort, INCLUDEARROW("pid", sortedby));
#ifdef CAN_GET_PROCESS_SIZE
    DETERMINEREVSORT("Process_size");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Process Size%s</a></TH>", COLORFORHEADER("process_size", sortedby), nextsort, INCLUDEARROW("process_size", sortedby));
#endif
    DETERMINEREVSORT("Machine");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">Machine%s</a></TH>", COLORFORHEADER("machine", sortedby), nextsort, INCLUDEARROW("machine", sortedby));
    DETERMINEREVSORT("Addr");
    printf("<TH %s><a href=\"/?mode=sessions&sortby=%s\">IP%s</a></TH>", COLORFORHEADER("addr", sortedby), nextsort, INCLUDEARROW("addr", sortedby));

    printf("</TR>\n");
    for (x = 0; x < len; x++) {
	printf("<TR>\n");
	printf("<TD><tt>&nbsp;&nbsp;</tt><a target=bottom href=\"/?mode=specsession&searchfor=%s&column=username\">%s</a></TD>\n", array[x]->username, array[x]->username);
	printf("<TD align=right><tt><a target=bottom href=\"/?mode=specsession&searchfor=%d&column=uid\">%d</a>&nbsp;</TT></TD>\n", array[x]->uid, array[x]->uid);
	printf("<TD><tt>&nbsp;&nbsp;</tt><a target=bottom href=\"/?mode=specsession&searchfor=%s&column=groupname\">%s</a></TD>\n", array[x]->groupname, array[x]->groupname);
	printf("<TD align=right><tt><a target=bottom href=\"/?mode=specsession&searchfor=%d&column=gid\">%d</a>&nbsp;</tt></TD>\n", array[x]->gid, array[x]->gid);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->session_count);
	printf("<TD align=right><tt><a target=bottom href=\"/?mode=specsession&searchfor=%d&column=pid\">%d</a>&nbsp;</tt></TD>\n", array[x]->pid, array[x]->pid);
#ifdef CAN_GET_PROCESS_SIZE
	printf("<TD align=right><tt>%s&nbsp;</tt></TD>\n", bytestostr(array[x]->process_size));
#endif
	printf("<TD><tt>&nbsp;&nbsp;</tt><a target=bottom href=\"/?mode=specsession&searchfor=%s&column=machine\">%s</a></TD>\n", array[x]->machine, array[x]->machine);
	printf("<TD><tt>&nbsp;&nbsp;</tt><a target=bottom href=\"/?mode=specsession&searchfor=%ld&column=addr\"><TT>%s</tt></a></TD>\n", array[x]->addr, inet_ntoa(*((struct in_addr *) &array[x]->addr)));
	printf("</TR>\n");
    }
    printf("</table></center>\n");
}

void formattable_specsession(struct crec_element *array[], int len, char *sortedby)
{
    int x;
    char *imgtag = get_sort_direction_img(sortedby);
    pstring surl;
    pstring nextsort;

    snprintf(surl, 1024, "mode=specsession&searchfor=%s&column=%s", cgi_variable("searchfor"), cgi_variable("column"));

    printf("Sessions that match %s %s:\n", cgi_variable("column"), cgi_variable("searchfor"));
    printf("<center><table" TABLEOPTS ">\n");

    printf("<TR bgcolor=%s>\n", TABLEHEADERCOLOR);

    DETERMINEREVSORT("Name");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Service%s</a></TH>", COLORFORHEADER("name", sortedby), surl, nextsort, INCLUDEARROW("name", sortedby));
    DETERMINEREVSORT("Username");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Username%s</a></TH>", COLORFORHEADER("username", sortedby), surl, nextsort, INCLUDEARROW("username", sortedby));
    DETERMINEREVSORT("Uid");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Uid%s</a></TH>", COLORFORHEADER("uid", sortedby), surl, nextsort, INCLUDEARROW("uid", sortedby));
    DETERMINEREVSORT("Groupname");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Group%s</a></TH>", COLORFORHEADER("groupname", sortedby), surl, nextsort, INCLUDEARROW("groupname", sortedby));
    DETERMINEREVSORT("Gid");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Gid%s</a></TH>", COLORFORHEADER("gid", sortedby), surl, nextsort, INCLUDEARROW("gid", sortedby));
    DETERMINEREVSORT("Pid");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Process%s</a></TH>", COLORFORHEADER("pid", sortedby), surl, nextsort, INCLUDEARROW("pid", sortedby));
#ifdef CAN_GET_PROCESS_SIZE
    DETERMINEREVSORT("Process_size");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Process size%s</a></TH>", COLORFORHEADER("process_size", sortedby), surl, nextsort, INCLUDEARROW("process_size", sortedby));
#endif
    DETERMINEREVSORT("Machine");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Machine%s</a></TH>", COLORFORHEADER("machine", sortedby), surl, nextsort, INCLUDEARROW("machine", sortedby));
    DETERMINEREVSORT("Addr");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">IP%s</a></TH>", COLORFORHEADER("addr", sortedby), surl, nextsort, INCLUDEARROW("addr", sortedby));
    DETERMINEREVSORT("Start");
    printf("<TH %s><a href=\"/?%s&sortby=%s\">Connect Time%s</a></TH>", COLORFORHEADER("start", sortedby), surl, nextsort, INCLUDEARROW("start", sortedby));
    printf("</TR>\n");
    for (x = 0; x < len; x++) {
	printf("<TR>\n");
	printf("<TD><tt>&nbsp;&nbsp;%s</tt></TD>\n", array[x]->service);
	printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->username);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->uid);
	printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->groupname);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->gid);
	printf("<TD align=right><tt>%d&nbsp;</tt></TD>\n", array[x]->pid);
#ifdef CAN_GET_PROCESS_SIZE
	printf("<TD align=right><tt>%s&nbsp;</tt></TD>\n", bytestostr(array[x]->process_size));
#endif
	printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", array[x]->machine);
	printf("<TD><TT>&nbsp;&nbsp;%s</tt></TD>\n", inet_ntoa(*((struct in_addr *) &array[x]->addr)));
	printf("<TD><tt>&nbsp;&nbsp;</tt>%s</TD>\n", format_datetime(array[x]->start));
	printf("</TR>\n");
    }
    printf("</table>\n");
}

void do_sessions(struct crec_element *crec_array[], int crec_array_len)
{
    int sessioncnt = 0;
    struct crec_element **sessions = calloc(crec_array_len, sizeof(struct crec_element *));
    char *c = cgi_variable("sortby");
    compare_function cmpfunc = NULL;

    if (!crec_array_len) {
	printf("No open sessions\n");
	return;
    }
    if (!sessions)
	error_exit(3);

    sessioncnt = get_unique_session_listing( /* crec_array, crec_array_len, */ sessions);
    if (!sessioncnt) {
	printf("No sessions\n");
	return;
    }
    if (c) {
	COMPARESORT(cmpfunc, crec, username, "username")
	    COMPARESORT(cmpfunc, crec, uid, "uid")
	    COMPARESORT(cmpfunc, crec, groupname, "groupname")
	    COMPARESORT(cmpfunc, crec, gid, "gid")
	    COMPARESORT(cmpfunc, crec, pid, "pid")
	    COMPARESORT(cmpfunc, crec, process_size, "process_size")
	    COMPARESORT(cmpfunc, crec, session_count, "session_count")
	    COMPARESORT(cmpfunc, crec, machine, "machine")
	    COMPARESORT(cmpfunc, crec, addr, "addr")
    }
    if (!cmpfunc) {
	cmpfunc = sort_crec_by_username_asc;
	c = "Username";
    }
    qsort(sessions, sessioncnt, sizeof(struct crec_element *), cmpfunc);
    print_document_header();
    formattable_sessions(sessions, sessioncnt, c);

}

void do_specsession(struct crec_element *array[], int len)
{
    char *column = cgi_variable("column");
    char *searchfor = cgi_variable("searchfor");
    struct crec_element **matches = calloc(len, sizeof(struct crec_element *));
    int num_matches;
    char *c = cgi_variable("sortby");
    compare_function cmpfunc = NULL;

    if (!matches)
	error_exit(3);

    if (!column || !searchfor) {
	printf("column or searchfor not set\n");
	return;
    }
    if (strcmp(column, "pid") == 0) {
	num_matches = sessions_that_match_pid(atoi(searchfor), array, len, matches);
    } else if (strcmp(column, "uid") == 0) {
	num_matches = sessions_that_match_uid(atoi(searchfor), array, len, matches);
    } else if (strcmp(column, "gid") == 0) {
	num_matches = sessions_that_match_gid(atoi(searchfor), array, len, matches);
    } else if (strcmp(column, "username") == 0) {
	num_matches = sessions_that_match_username(searchfor, array, len, matches);
    } else if (strcmp(column, "groupname") == 0) {
	num_matches = sessions_that_match_groupname(searchfor, array, len, matches);
    } else if (strcmp(column, "machine") == 0) {
	num_matches = sessions_that_match_machine(searchfor, array, len, matches);
    } else if (strcmp(column, "addr") == 0) {
	num_matches = sessions_that_match_addr(atoi(searchfor), array, len, matches);
    } else if (strcmp(column, "service") == 0) {
	num_matches = sessions_that_match_service(searchfor, array, len, matches);
    } else {
	printf("%s is not able to searched on.\n", column);
	return;
    }

    if (!num_matches) {
	printf("No sessions found matching %s %s\n", column, searchfor);
	return;
    }
    if (c) {
	COMPARESORT(cmpfunc, crec, username, "username")
	    COMPARESORT(cmpfunc, crec, uid, "uid")
	    COMPARESORT(cmpfunc, crec, groupname, "groupname")
	    COMPARESORT(cmpfunc, crec, gid, "gid")
	    COMPARESORT(cmpfunc, crec, pid, "pid")
	    COMPARESORT(cmpfunc, crec, process_size, "process_size")
	    COMPARESORT(cmpfunc, crec, session_count, "session_count")
	    COMPARESORT(cmpfunc, crec, machine, "machine")
	    COMPARESORT(cmpfunc, crec, addr, "addr")
	    COMPARESORT(cmpfunc, crec, start, "start")
	    COMPARESORT(cmpfunc, crec, service, "service")
    }
    if (!cmpfunc) {
	cmpfunc = sort_crec_by_service_asc;
	c = "Name";
    }
    qsort(matches, num_matches, sizeof(struct crec_element *), cmpfunc);
    formattable_specsession(matches, num_matches, c);
}

void print_header(void)
{
    if (!cgi_waspost()) {
	printf("Expires: 0\r\n");
    }
}

void body_start(void)
{
    printf("<body LINK=" LINKCOLOR " VLINK=" VLINKCOLOR ">\n");
}
void body_end(void)
{
    printf("</body>\n");
}

int main(int argc, char *argv[])
{
    int c;
    static pstring servicesf = CONFIGFILE;
    extern char *optarg;
    char *cv;

    TimeInit();
    setup_logging(argv[0], True);

    charset_initialise();

    DEBUGLEVEL = 0;
    dbf = sys_fopen("/dev/null", "w");
    if (!dbf)
	dbf = stderr;

    if (getuid() != geteuid()) {
	printf("smbwebstatus should not be run setuid\n");
	return (1);
    }
    while ((c = getopt(argc, argv, "s:")) != EOF) {
	switch (c) {
	case 's':
	    pstrcpy(servicesf, optarg);
	    break;
	default:
	    fprintf(stderr, "Usage: %s [-s servicesfile]\n", *argv);
	    return (-1);
	}
    }

    get_myname(myhostname, NULL);

    if (!lp_load(servicesf, False, False, False)) {
	fprintf(stderr, "Can't load %s - run testparm to debug it\n", servicesf);
	return (-1);
    }
    cgi_setup("/tmp", (0 && "no auth"));

    print_header();

#define DEBUG_COMMENTS
    cgi_load_variables(NULL);

    cv = cgi_pathinfo();
    if (strcmp(cv, "arrow_up.gif") == 0) {
	send_gif_image(arrow_up_gif, AUPGIFLEN);
	exit(0);
    }
    if (strcmp(cv, "arrow_down.gif") == 0) {
	send_gif_image(arrow_down_gif, ADNGIFLEN);
	exit(0);
    }
    read_crec_status_file();
    read_share_list_file();
    read_locked_files();

    printf("Content-type: text/html\r\n\r\n");
    printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n");
    printf("<HTML>\n<HEAD><TITLE>smbwebstatus: %s</TITLE></HEAD>\n\n", myhostname);

    // if (!crec_ptr_array) {
    // printf("No status information available.\n");
    // return 0;
    // }

    /* hey, maybe convert these to function pointers */
    cv = cgi_variable("mode");
    if (!cv) {
	do_frames();
    } else if (strcmp(cv, "sessions") == 0) {
	body_start();
	do_sessions(crec_ptr_array, crec_list_len);
	body_end();
    } else if (strcmp(cv, "specsession") == 0) {
	body_start();
	do_specsession(crec_ptr_array, crec_list_len);
	body_end();
    } else if (strcmp(cv, "resources") == 0) {
	determine_open_and_locks_for_defined_services();
	body_start();
	do_resources(crec_ptr_array, crec_list_len);
	body_end();
    } else if (strcmp(cv, "lockedfiles") == 0) {
	body_start();
	do_locked_files(crec_ptr_array, crec_list_len);
	body_end();
    } else if (strcmp(cv, "speclock") == 0) {
	body_start();
	do_spec_locked_file(crec_ptr_array, crec_list_len);
	body_end();
    } else if (strcmp(cv, "lockedlist") == 0) {
	body_start();
	do_lockedfile_list();
	body_end();
    } else if (strcmp(cv, "empty") == 0) {
	body_start();
	printf("\n");		/* to generate an empty frame */
	body_end();
    } else {
	do_frames();
    }

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

    return 0;

}
