/*
 * Copyright (c) 2003-2012
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*****************************************************************************
 * COPYRIGHT AND PERMISSION NOTICE
 * 
 * Copyright (c) 2001-2003 The Queen in Right of Canada
 * 
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, and/or sell
 * copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, provided that the above copyright notice(s) and this
 * permission notice appear in all copies of the Software and that both the
 * above copyright notice(s) and this permission notice appear in supporting
 * documentation.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE 
 * BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, 
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS 
 * SOFTWARE.
 * 
 * Except as contained in this notice, the name of a copyright holder shall not
 * be used in advertising or otherwise to promote the sale, use or other
 * dealings in this Software without prior written authorization of the
 * copyright holder.
 ***************************************************************************/

/*
 * Because this code can modify group information, we assume that it has
 * already been subjected to access control.
 * This implementation puts each group definition in its own file.
 * Eventually, it might be more efficient to separate this into two files
 * per definition, one for meta information and the other for the members.
 *
 * XXX
 * Add constraint check to see if updates are permitted by ACL
 * The result of a group resolution could be cached so resolution does not
 * have to be repeated for each query.
 * Need to guard against concurrent updates
 * Need to guard against file corruption, eg. by writing to a temp file and
 * then renaming it.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2012\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: group.c 2594 2012-10-19 17:28:49Z brachman $";
#endif

#include "dacs.h"

static char *log_module_name = "dacs_group";

extern char *group_errmsg;

static int emit_xml = -1;
static char *acs_constraint = NULL;
static char *default_acs_constraint = NULL;

static void
emit_xml_response_header(FILE *fp, char *dtd)
{

  if (emit_xml_header(fp, dtd))
	fprintf(fp, "<%s>\n", make_xml_root_element(dtd));
}

static void
emit_xml_response_trailer(FILE *fp)
{

  fprintf(fp, "</dacs_group>\n");	
  emit_xml_trailer(fp);
}

static void
emit_failure(FILE *fp)
{
  Common_status cs;

  if (emit_xml == -1)
	return;

  if (emit_xml) {
	emit_xml_response_header(fp, "dacs_group");
	init_common_status(&cs, NULL, NULL, group_errmsg);
	fprintf(fp, "%s", make_xml_common_status(&cs));
	emit_xml_response_trailer(fp);
  }
  else {
	emit_html_header(fp, NULL);
	fprintf(fp, "<P>An error occurred during the service request.\n");
	if (group_errmsg != NULL)
	  fprintf(fp, "<P>Reason: %s.\n", group_errmsg);
	emit_html_trailer(fp);
  }
}

static void
emit_success(FILE *fp)
{

  if (emit_xml == -1)
	return;

  if (emit_xml) {
	emit_xml_response_header(fp, "dacs_group");
	fprintf(fp, "<ok/>\n");
	emit_xml_response_trailer(fp);
  }
  else {
	emit_html_header(fp, NULL);
	fprintf(fp, "<P>Service request completed.\n");
	emit_html_trailer(fp);
  }
}

static int
do_create_group(FILE *fp, Kwv *kwv)
{
  int st;
  Kwv_pair *gname, *gtype;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL)
	return(-1);
  if ((gtype = kwv_lookup(kwv, "GROUP_TYPE")) == NULL)
	return(-1);

  st = dacs_create_group(conf_val(CONF_JURISDICTION_NAME), gname->val,
						 gtype->val);

  if (st == 0)
	emit_success(fp);

  return(st);
}

static int
do_delete_group(FILE *fp, Kwv *kwv)
{
  char *jurisdiction, *name;
  Kwv_pair *gname;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL)
	return(-1);
  if (!is_valid_name(gname->val))
	return(-1);

  jurisdiction = conf_val(CONF_JURISDICTION_NAME);
  name = gname->val;

  if (dacs_delete_group(jurisdiction, name) == -1)
	return(-1);

  emit_success(fp);
  return(0);
}

static int
do_list_groups(FILE *fp, Kwv *kwv)
{
  int i;
  char *j;
  Dsvec *dsv;
  Group_file *gf;

  j = kwv_lookup_value(kwv, "JURISDICTION");

  if (dacs_list_groups(j, &dsv) == NULL)
	return(-1);

  if (emit_xml)
	emit_xml_response_header(fp, "dacs_group");
  else {
	emit_html_header(fp, NULL);
	fprintf(fp, "<center>");
	fprintf(fp, "<b>Groups</b><p>");
	fprintf(fp, "<table border>\n");
	fprintf(fp, "<tr><td align=\"center\">Jurisdiction</td>");
	fprintf(fp, "<td align=\"center\">Group Name</td>");
	fprintf(fp, "<td align=\"center\">Group Type</td></tr>\n");
  }

  for (i = 0; i < dsvec_len(dsv); i++) {
	gf = (Group_file *) dsvec_ptr_index(dsv, i);
	if (emit_xml)
	  fprintf(fp,
			  "<group_member jurisdiction=\"%s\" name=\"%s\" type=\"%s\"/>\n",
			  gf->group_name.jurisdiction,
			  gf->group_name.username,
			  gf->type == GROUP_DEFINITION_TYPE_PRIVATE
			  ? "private" : "public");
	else {
	  fprintf(fp, "<tr>");
	  fprintf(fp, "<td>%s</td>", gf->group_name.jurisdiction);
	  fprintf(fp, "<td>%s</td>", gf->group_name.username);
	  fprintf(fp, "<td>%s</td>\n",
			  gf->type == GROUP_DEFINITION_TYPE_PRIVATE
			  ? "private" : "public");
	  fprintf(fp, "</tr>\n");
	}
  }

  if (!emit_xml)
	fprintf(fp, "</table></center>\n");

  return(0);
}

static int
do_add_group_member(FILE *fp, Kwv *kwv)
{
  char *dacs;
  Kwv_pair *gname, *gmname, *mtype;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL)
	return(-1);
  if ((gmname = kwv_lookup(kwv, "MEMBER_NAME")) == NULL)
	return(-1);
  if ((mtype = kwv_lookup(kwv, "MEMBER_TYPE")) == NULL)
	return(-1);
  dacs = kwv_lookup_value(kwv, "DACS");

  if (dacs_add_group_member(conf_val(CONF_JURISDICTION_NAME),
							gname->val, gmname->val, mtype->val, dacs) == -1)
	return(-1);

  emit_success(fp);

  return(0);
}

static int
do_delete_group_member(FILE *fp, Kwv *kwv)
{
  char *jurisdiction, *name;
  Kwv_pair *gname, *mname;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL)
	return(-1);
  if (!is_valid_name(gname->val))
	return(-1);
  if ((mname = kwv_lookup(kwv, "MEMBER_NAME")) == NULL)
	return(-1);
  if (group_member_name_from_str(mname->val, &jurisdiction, &name) == -1)
	return(-1);

  if (dacs_delete_group_member(gname->val, jurisdiction, name) == -1)
	return(-1);

  emit_success(fp);

  return(0);
}

/*
 * Show the definition of a group (that is, the unresolved membership).
 */
static int
do_show_group_definition(FILE *fp, Kwv *kwv)
{
  char *buf, *j;
  Groups *groups;
  Kwv_pair *gname;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL) {
	group_errmsg = "Missing GROUP_NAME";
	return(-1);
  }
  if (!is_valid_name(gname->val)) {
	group_errmsg = "Invalid GROUP_NAME";
	return(-1);
  }

  if ((j = kwv_lookup_value(kwv, "JURISDICTION")) == NULL)
	j = conf_val(CONF_JURISDICTION_NAME);

  if (dacs_get_group_definition(j, gname->val, &buf) == -1) {
	group_errmsg = "Couldn't get the group definition";
	return(-1);
  }

  if (emit_xml) {
	emit_xml_response_header(fp, "dacs_group");
	fprintf(fp, "%s", buf);
  }
  else {
	emit_html_header(fp, NULL);
	if (parse_xml_groups(buf, &groups) == -1) {
	  group_errmsg = "Group file parse error";
	  return(-1);
	}

	groups_xml_html(fp, groups);
  }

  return(0);
}

static int
do_show_group_membership(FILE *fp, Kwv *kwv)
{
  char *j;
  Groups *groups;
  Kwv_pair *gname;

  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL) {
	group_errmsg = "Missing GROUP_NAME";
	return(-1);
  }
  if (!is_valid_name(gname->val)) {
	group_errmsg = "Invalid GROUP_NAME";
	return(-1);
  }

  if ((j = kwv_lookup_value(kwv, "JURISDICTION")) == NULL)
	j = conf_val(CONF_JURISDICTION_NAME);

  emit_html_header(fp, NULL);
  if (dacs_group_get_membership(j, gname->val, &groups) == -1)
	return(-1);

  groups_xml_html(fp, groups);

  return(0);
}

/*
 * Handle a request from another jurisdiction to cache one or more group
 * definitions, passed as a "groups" element.
 */
static int
do_receive_group_definition(FILE *fp, Kwv *kwv)
{
  char *groups_str;

  if ((groups_str = kwv_lookup_value(kwv, "GROUPS")) == NULL) {
	group_errmsg = "No GROUPS parameter";
	return(-1);
  }

  if (dacs_receive_group_definition(conf_val(CONF_JURISDICTION_NAME),
									groups_str) == -1)
	return(-1);

  emit_success(fp);

  return(0);
}

/*
 * Send group definitions (master or cached copies) to the jurisdiction
 * making the service request.
 */
static int
do_send_group_definition(FILE *fp, Kwv *kwv)
{
  char *str;
  Groups *groups;

  if ((str = kwv_lookup_value(kwv, "GROUP_NAME_LIST")) == NULL) {
	log_msg((LOG_ERROR_LEVEL,
			 "do_send_group_definition: missing GROUP_NAME_LIST parameter"));
	return(-1);
  }

  if (dacs_send_group_definition(str, &groups) == -1)
	return(-1);

  if (groups != NULL && groups->group_definition_head != NULL) {
	if (emit_xml)
	  groups_xml_text(fp, groups);
	else
	  groups_xml_html(fp, groups);
  }

  return(0);
}

/*
 * Delete a cached group definition.
 */
static int
do_purge_group(FILE *fp, Kwv *kwv)
{
  char *j;
  Kwv_pair *gname;

  if ((j = kwv_lookup_value(kwv, "JURISDICTION")) == NULL)
	return(-1);
  if (streq(j, conf_val(CONF_JURISDICTION_NAME)))
	return(-1);
  if (!is_valid_jurisdiction_name(j))
	return(-1);
  if ((gname = kwv_lookup(kwv, "GROUP_NAME")) == NULL
	  || !is_valid_name(gname->val))
	return(-1);

  return(dacs_delete_group(j, gname->val));
}

static int
do_change_group_definition(FILE *fp, Kwv *kwv)
{
  char *gname, *ngname;

  gname = kwv_lookup_value(kwv, "GROUP_NAME");
  ngname = kwv_lookup_value(kwv, "NEW_GROUP_NAME");

  if (dacs_change_group_definition(conf_val(CONF_JURISDICTION_NAME),
								   gname, ngname) == -1)
	return(-1);

  emit_success(fp);

  return(0);
}

/*
 * Show which role-based groups the caller belongs to.
 */
static int
do_show_roles(FILE *fp, Kwv *kwv)
{
  int i;
  char *remote_addr;
  Cookie *cookies;
  unsigned int ncookies;
  Credentials *credentials, *selected;
  Roles_list *rl, *roles_list;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	group_errmsg = "No REMOTE_ADDR found";
	return(-1);
  }

  if (get_cookies(NULL, &cookies, &ncookies) == -1) {
	group_errmsg = "Cookie parse error";
	return(-1);
  }

  if (ncookies == 0) {
	group_errmsg = "No DACS cookies found";
	return(-1);
  }

  if (get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL) < 1) {
	group_errmsg = "No valid credentials";
	return(-1);
  }

  if (dacs_get_roles(selected, &roles_list) == -1)
	return(-1);

  for (rl = roles_list; rl != NULL; rl = rl->next) {
	if (rl->group_names != NULL) {
	  for (i = 0; rl->group_names[i] != NULL; i++) {
		if (emit_xml) {
		  fprintf(fp, "<group_member ");
		  fprintf(fp, "jurisdiction=\"%s\" name=\"%s\" type=\"role\">\n",
				  rl->group_names[i]->jurisdiction,
				  rl->group_names[i]->username);
		}
		else
		  fprintf(fp, "<br>%s\n",
				  auth_identity(NULL, rl->group_names[i]->jurisdiction,
								rl->group_names[i]->username, NULL));
	  }
	}
  }

  return(0);
}

/*
 * Show which defined groups user MEMBER_NAME belongs to, as known by this
 * jurisdiction.
 * MATCH_JURISDICTION can be set to limit testing to those groups defined
 * by the given jurisdiction.
 * MATCH_GROUP_NAME can be set to a regular expression, applied to each known
 * group name from any jurisdiction, to limit testing to those groups that
 * match the given regex.
 */
static int
do_list_group_membership(FILE *fp, Kwv *kwv)
{
  int i;
  char *match_jurisdiction, *match_name, *gmname;
  Dsvec *dsv;
  Group_file *gf;

  if ((gmname = kwv_lookup_value(kwv, "MEMBER_NAME")) == NULL) {
	group_errmsg = "MEMBER_NAME parameter not provided";
	return(-1);
  }
  match_jurisdiction = kwv_lookup_value(kwv, "MATCH_JURISDICTION");
  match_name = kwv_lookup_value(kwv, "MATCH_GROUP_NAME");

  if (dacs_list_group_membership(gmname, match_jurisdiction, match_name, &dsv)
	  == -1)
	return(-1);

  if (emit_xml)
	emit_xml_response_header(fp, "dacs_group");
  else
	emit_html_header(fp, NULL);

  for (i = 0; i < dsvec_len(dsv); i++) {
	gf = (Group_file *) dsvec_ptr_index(dsv, i);
	if (emit_xml)
	  fprintf(fp,
			  "<group_member jurisdiction=\"%s\" name=\"%s\" type=\"%s\"/>\n",
			  gf->group_name.jurisdiction,
			  gf->group_name.username,
			  (gf->type == GROUP_DEFINITION_TYPE_PRIVATE)
			  ? "private" : "public");
	else
	  fprintf(fp, "%s ",
			  auth_identity(NULL, gf->group_name.jurisdiction,
							gf->group_name.username, NULL));
  }

  return(0);
}

/*
 * Test whether the caller is in a given group.
 */
static int
do_test_group_membership(FILE *fp, Kwv *kwv)
{
  int st;
  char *gname, *remote_addr;
  Cookie *cookies;
  unsigned int ncookies;
  Credentials *credentials, *selected;

  if ((remote_addr = getenv("REMOTE_ADDR")) == NULL) {
	group_errmsg = "No REMOTE_ADDR found";
	return(-1);
  }

  if ((gname = kwv_lookup_value(kwv, "GROUP")) == NULL) {
	group_errmsg = "GROUP parameter not provided";
	return(-1);
  }

  if (get_cookies(NULL, &cookies, &ncookies) == -1) {
	group_errmsg = "Cookie parse error";
	return(-1);
  }

  if (ncookies == 0) {
	group_errmsg = "No DACS cookies found";
	return(-1);
  }

  if (get_valid_scredentials(cookies, remote_addr, 0, &credentials,
							 &selected, NULL) < 1) {
	group_errmsg = "No valid credentials";
	return(-1);
  }

  if ((st = dacs_test_group_membership(selected, gname)) == -1)
	return(-1);

  emit_html_header(fp, NULL);
  if (st == 1)
	fprintf(fp, "<BR>Yes\n");
  else
	fprintf(fp, "<BR>No\n");

  return(0);
}

static int
do_apply_deltas(FILE *fp, Kwv *kwv)
{

  if (dacs_apply_deltas(conf_val(CONF_JURISDICTION_NAME)) == -1)
	return(-1);

  emit_success(fp);

  return(0);
}

typedef struct Optab {
  char *op_name;
  int (*op_func)(FILE *, Kwv *);
  int op_isupdate;
} Optab;

Optab optab[] = {
  { "SHOW_GROUP_MEMBERSHIP",    do_show_group_membership,    0 },
  { "LIST_GROUPS",              do_list_groups,              0 },
  { "SHOW_GROUP_DEFINITION",    do_show_group_definition,    0 },
  { "SHOW_ROLES",               do_show_roles,               0 },
  { "TEST_GROUP_MEMBERSHIP",    do_test_group_membership,    0 },
  { "LIST_GROUP_MEMBERSHIP",    do_list_group_membership,    0 },
  { "CREATE_GROUP",             do_create_group,             1 },
  { "DELETE_GROUP",             do_delete_group,             1 },
  { "ADD_GROUP_MEMBER",         do_add_group_member,         1 },
  { "DELETE_GROUP_MEMBER",      do_delete_group_member,      1 },
  { "PURGE_GROUP",              do_purge_group,              1 },
  { "CHANGE_GROUP_DEFINITION",  do_change_group_definition,  1 },
  { "RECEIVE_GROUP_DEFINITION", do_receive_group_definition, 1 },
  { "SEND_GROUP_DEFINITION",    do_send_group_definition,    0 },
  { "APPLY_DELTAS",             do_apply_deltas,             1 },
  { NULL,                       NULL,                        0 }
};

int
main(int argc, char **argv)
{
  int i, st;
  Kwv *kwv;
  Kwv_pair *op;

  group_errmsg = "Internal error";
  st = -1;

  if (dacs_init(DACS_WEB_SERVICE, &argc, &argv, &kwv, &group_errmsg) == -1) {
  fail:
	emit_failure(stdout);
	log_msg((LOG_ERROR_LEVEL, "Failed: %s", group_errmsg));
	exit(1);
  }

  if (should_use_argv) {
	if (argc > 1) {
	  group_errmsg = "Usage: unrecognized parameter";
	  goto fail;
	}
  }

  /* Do this as early as possible. */
  emit_xml = test_emit_xml_format();

  if ((op = kwv_lookup(kwv, "OPERATION")) == NULL) {
	group_errmsg = "Missing OPERATION";
	goto fail;
  }

  acs_constraint = getenv("DACS_CONSTRAINT");
  default_acs_constraint = getenv("DACS_DEFAULT_CONSTRAINT");

  if (emit_xml)
	emit_xml_response_header(stdout, "dacs_group");
  else
	emit_html_header(stdout, NULL);

  /* Try to execute the requested operation. */
  for (i = 0; optab[i].op_name != NULL; i++) {
	if (strcaseeq(op->val, optab[i].op_name)) {
	  /* Permit non-updating operations to all. */
	  if (optab[i].op_isupdate) {
		if (acs_constraint == NULL
			|| !strcaseeq(acs_constraint, "allow-updates")) {
		  log_msg((LOG_ERROR_LEVEL,
				   "Operation '%s' requires update capability\n", op->val));
#ifdef NOTDEF
		  group_errmsg = "Permission denied";
		  goto fail;
#endif
		}
	  }
	  st = (optab[i].op_func)(stdout, kwv);
	  break;
	}
  }

  if (optab[i].op_name == NULL) {
	group_errmsg = "Unrecognized operation";
	goto fail;
  }

  if (st == -1) {
	log_msg((LOG_ERROR_LEVEL, "Operation failed: %s", optab[i].op_name));
	if (group_errmsg == NULL)
	  group_errmsg = "Could not complete the operation";
	goto fail;
  }

  if (emit_xml)
	emit_xml_response_trailer(stdout);
  else
	emit_html_trailer(stdout);

  exit(0);
}
