/*
    slforum version 1.x - Simple chat room usable via telnet.
    Copyright (C) 1998-2006 Jeremy D. Monin, Dan Chin, Matt Lochansky
	(forum@shadowlands.org).
    Based on jdkchat version 1.3, J.D.Koftinoff Software, Ltd. (GPL'd)

    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.
*/


/* This file is meant to be #included into slforumuser.cc */




/** Ensure AT ALL TIMES that this table corresponds to the typedef
 *  for enum admin_substate_t in slforumuser.h; it must have the right
 *  number of substates, in the right order.
 *  For any substate which doesn't involve waiting for a line of input from
 *  the input line, use { false, ss_unused } or {true, ss_unused} and put
 *  code to transition out of your substate into HandleAdminLine().
 */

/* static */ const admin_input_substate_behav_t
	SLForumUser::admInputSSBehavTable[] =
{
  /* fields: { show_input, nextstate } */

  { true, ss_unused },  /* ss_nonadmin */

/* menus */
  { false, ss_unused },  /* ss_mainmenu */

/* add */
  { true,  ss_addgetpw      },  /* ss_addgetuname   */
  { false, ss_addconfpw     },  /* ss_addgetpw      */
  { false, ss_addgetgecos   },  /* ss_addconfpw     */
  { true,  ss_addgetsponsor },  /* ss_addgetgecos   */
  { true,  ss_addgetemail   },  /* ss_addgetsponsor */
  { true,  ss_addaskadmin   },  /* ss_addgetemail   */
  { false, ss_unused        },  /* ss_askadmin      */
  { false, ss_unused        },  /* ss_add_doit      */

/* edit */
  { true,  ss_edit_retrieve    },  /* ss_editgetuname     */
  { false, ss_unused           },  /* ss_edit_retrieve    */
  { false, ss_unused           },  /* ss_editmenu         */
  { true,  ss_editconfuname    },  /* ss_edituname        */
  { false, ss_unused           },  /* ss_editconfuname    */
  { false, ss_unused           },  /* ss_edituname_doit   */
  { false, ss_editconfpw       },  /* ss_editpw           */
  { false, ss_editpw_doit      },  /* ss_editconfpw       */
  { false, ss_unused           },  /* ss_editpw_doit      */
  { true,  ss_editgecos_doit   },  /* ss_editgecos        */
  { false, ss_unused           },  /* ss_editgecos_doit   */
  { true,  ss_editsponsor_doit },  /* ss_editsponsor      */
  { false, ss_unused           },  /* ss_editsponsor_doit */
  { true,  ss_editemail_doit   },  /* ss_editemail        */
  { false, ss_unused           },  /* ss_editemail_doit   */
  { false, ss_unused           },  /* ss_editisadmin      */
  { false, ss_unused           },  /* ss_edit_doit        */

/* del */
  { true,  ss_dellookup },  /* ss_delgetuname */
  { false, ss_unused    },  /* ss_dellookup   */
  { false, ss_unused    },  /* ss_delconfirm  */
  { false, ss_unused    },  /* ss_del_doit    */

/* list */
  { false, ss_unused     },  /* ss_list    */

/* !set password (for virtusers) */
  { false, ss_pwch_getnewpw  },  /* ss_pwch_getoldpw  */
  { false, ss_pwch_confnewpw },  /* ss_pwch_getnewpw  */
  { false, ss_pwch_setnewpw  },  /* ss_pwch_confnewpw */
  { false, ss_unused         },  /* ss_pwch_setnewpw  */

  /* macro expand and edit (user-visible, not just admins) */
  { false, ss_unused         },  /* ss_macro_expand_readkey    */
  { false, ss_unused         },  /* ss_macro_addedit_readkey   */
  { false, ss_unused         },  /* ss_macro_addedit_checkkey  */
  { false, ss_unused         },  /* ss_macro_addedit_askreplace*/
  { true,  ss_macro_addedit_checkline },  /* ss_macro_addedit_readline */
  { false, ss_unused         },  /* ss_macro_addedit_ctrlq     */
  { false, ss_unused         },  /* ss_macro_addedit_checkline */
  { false, ss_unused         },  /* ss_macro_addedit_askcrlf   */
  { false, ss_unused         },  /* ss_macro_addedit_doit      */
  { false, ss_unused         },  /* ss_macro_delete_readkey    */
  { false, ss_unused         },  /* ss_macro_delete_confirm    */

  /* 'going idle soon, hit any key to not' message */
  { false, ss_unused         },  /* ss_idlesoon_hitanykey      */

  /* end-of-table marker (constructor checks for this if
   * checked_admin_ss_table is false; if it's not in the
   * 'right place', we know we've either changed this table
   * or changed admin_substate_t, and made a mistake
   * in doing so.)
   */
  { true,  ss_unused        },   /* ss_NUM_SUBSTATES */
  { false, SS_NUM_SUBSTATES }    /* ss_unused */
};


// for convenience/clarity in edituser handling

#define SD         ((struct slf_userinfo *)statedata)
#define SD_name    ((struct slf_userinfo *)statedata)->u.pw_name
#define SD_passwd  ((struct slf_userinfo *)statedata)->u.pw_passwd
#define SD_gid     ((struct slf_userinfo *)statedata)->u.pw_gid
#define SD_gecos   ((struct slf_userinfo *)statedata)->u.pw_gecos
#define SD_dir     ((struct slf_userinfo *)statedata)->u.pw_dir
#define SD_shell   ((struct slf_userinfo *)statedata)->u.pw_shell
#define SD_sponsor ((struct slf_userinfo *)statedata)->uv_sponsor
#define SD_email   ((struct slf_userinfo *)statedata)->uv_email



// TODO TODO TODO most of these should check what substate
// they're coming from to make sure it's the right one...

/** Change admin substate, and optionally process some state transition code.
 *  (if just_set_substate=false).  Note that if going to ss_nonadmin,
 *  the input buffer will be CLEARED for security reasons unless you modify
 *  the if statement at the bottom of this method.  (It's currently kept only
 *  when transitioning from ss_macro_expand_readkey or ss_idlesoon_hitanykey.)
 *
 *  Note that most of these handlers won't check to see
 *  if the user's actually an admin (since some of these
 *  are non-admin substates to change password),
 *  that's up to the caller.  (TODO TODO TODO use ss_notadmin_FIRST to check)
 */
void SLForumUser::setAdmSubstate
	(admin_substate_t subst, bool justSetState /* =false */ )
{
  if (! justSetState)
  {

  switch (subst)
    {

    ///////////// nonadmin //////////////

    case ss_nonadmin:
      {
	  if (admin_substate < ss_notadmin_FIRST)
	  {
	      room->logprintf("ADMIN: %s exits admin\n", name);
	      Send("Exiting the Admin System.", 0, false);
	  }
	  // Theoretically we could also check admin_substate
	  //   vs ss_macro_FIRST/ss_macro_LAST, but the macro
	  //   codes never get this far, so it isn't an issue.
	  //   They already have a way to not see the "exiting" message.
	  break;
      }


    ///////////// main menu //////////////

    case ss_mainmenu:
      {
	if (! is_admin) {
	  setAdmSubstate(ss_nonadmin);
	  return;
	}
	room->logprintf("ADMIN: %s at admin menu\n", name);
	Send("Admin menu:", 0, false);
	Send("A - add    L - list   E - edit   D - delete   Q - quit", 0 ,false);
	setAdmPrompt("Admin> ");
	if (admin_substate == ss_editmenu)
	{
	  free(SD_name);
	  free(SD_passwd);
	  free(SD_gecos);
	  if ((SD_sponsor)!=NULL) free((char*)SD_sponsor);
	  if ((SD_email)!=NULL)   free((char*)SD_email);
	  free(statedata);
	}
	break;
      }



#define SD_CHECK_VIRTUSERS_RUNNING   if (NULL == slf_virtuser_get_dbfname())  \
	{                                           \
		Send ("Virtual user database not available; check config.", \
		      0, true);                     \
		setAdmSubstate (ss_mainmenu);       \
		return;                             \
	}



    ///////////// add user //////////////


    case ss_addgetuname:
      {
	SD_CHECK_VIRTUSERS_RUNNING;     /* Returns to main menu if not */
	setAdmPrompt(" Username to Add> ");
	break;
      }



#define STRDUP_STORE(into_fld,src,reqd)  \
	if(reqd || ((src)[0] != '\0')) { \
	 ((struct slf_userinfo *)statedata)->into_fld = strdup((src));   \
	 if (NULL == (((struct slf_userinfo *)statedata)->into_fld)) {   \
	   room->Announce("Out of memory in ss_addgetpw handler"); \
	   free(statedata);                                        \
	   setAdmSubstate(ss_mainmenu);                            \
	   return;                                                 \
	 }                                                         \
	}
 

    case ss_addgetpw:
      {
	if (buf.cur_len == 0)
	{
	  Send("Add cancelled.", 0, false);
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	// TODO: make sure the username's well-formed

	// make sure name doesn't exist yet
	statedata = slf_virtuser_get_info(buf.buffer, NULL, false);
	// TODO: check for _real_ users (See L224)

	if (statedata != NULL)
	{
	  Send("A user by that name already exists.", 0, false);
	  statedata = NULL;
	  setAdmSubstate(ss_mainmenu);
	  return;
	}

	// allocate new struct slf_userinfo
	statedata = malloc(sizeof(struct slf_userinfo));
	if (statedata==NULL)
	{
	  room->Announce("Out of memory in ss_addgetpw handler");
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	memset (statedata, 0, sizeof(struct slf_userinfo));  // zero it out

	// store name
	STRDUP_STORE(u.pw_name, buf.buffer, true);

	room->logprintf("L194: %s starts creating new user %s\n", name, buf.buffer);

	// prompt for pw
	setAdmPrompt("        Password>");
      }
      break;

    case ss_addconfpw:
      {
	if (strlen(buf.buffer) < 6)
	{
	  Send("Password must be at least six characters, please try again.",
	       0, false);
	  setAdmPrompt("        Password>");
	  setAdmSubstate(ss_addgetpw, true);
	  return;
	}

	// store pw
	STRDUP_STORE(u.pw_passwd, buf.buffer, true);

	// wipe buf
	memset(buf.buffer, 0, sizeof(buf.buffer));
	// set prompt
	setAdmPrompt("Confirm password>");
      }
      break;

    case ss_addgetgecos:
      {
	// make sure pw matches
	if (0 != strcmp(buf.buffer, SD_passwd))
	{
	  Send ("Passwords don't match, please try again.");
	  setAdmPrompt("        Password>");
	  setAdmSubstate(ss_addgetpw, true);
	  return;
	}

	// free the other copy of it
	free( SD_passwd );

	// encrypt that pw
	SD_passwd = (char *) slf_cryptnewpw(buf.buffer);

	// set prompt
	setAdmPrompt("       Full name> ");
      }
      break;

    case ss_addgetsponsor:
      {
	// store gecos
	STRDUP_STORE(u.pw_gecos, buf.buffer, true);

	// set prompt
	setAdmPrompt(" Sponsoring user> ");
      }
      break;

    case ss_addgetemail:
      {
	// store sponsor
	STRDUP_STORE(uv_sponsor, buf.buffer, false);

	// set prompt
	setAdmPrompt("Virtuser's email address> ");
      }
      break;

    case ss_addaskadmin:
      {
	// store email
	STRDUP_STORE(uv_email, buf.buffer, false);

	// set prompt
	setAdmPrompt("Make new user an admin (Y/N)? ");
      }
      break;

    case ss_add_doit:
      {
	// HandleAdminLine has set the is-admin (gid) flag for us,
	// so we don't need to do that at least.

	// re-confirm no user there yet
	// TODO: check for _real_ users (see L133)
	{
	  const struct slf_userinfo *pw;
	  pw = slf_virtuser_get_info
	    ( SD_name, NULL, false);
	  if (pw != NULL)
	  {
	    Send("A user by that name has just been added!", 0, false);
	    free(SD_name);
	    free(SD_passwd);
	    free(SD_gecos);
	    if ((SD_sponsor)!=NULL) free((char*)SD_sponsor);
	    if ((SD_email)!=NULL)   free((char*)SD_email);
	    free(statedata);
	    setAdmSubstate(ss_mainmenu);
	    return;
	  }
	}

	room->logprintf("L279: %s gets to add that user, doing it now\n", name);

	// add user to db
	if (slf_virtuser_put_info( SD ) == false)
	{
		room->logprintf ("L282: %s slf_virtuser_put_info failed!\n", name);
	}

	// free data
	free(SD_name);
	free(SD_gecos);
	free(statedata);

	// mention success
	Send ("User added.", 0, false);
	setAdmSubstate(ss_mainmenu);
      }
      return;

#undef STRDUP_STORE





    ///////////// edit user //////////////


    case ss_edit_retrieve:
      {
	// get user
	if (strlen(buf.buffer) == 0)
	{
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	statedata = slf_virtuser_get_info(buf.buffer, NULL, true);
	if (statedata == NULL)
	{
	  Send("User not found.", 0, false);
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	room->logprintf("ADMIN: %s editing user %s\n", name, buf.buffer);
	setAdmSubstate(ss_editmenu);
	return;
      }


    case ss_editmenu:
      {
	// print user's info
	char buf[USERBUFSIZ];  buf[USERBUFSIZ-1] = '\0';
	sprintf(buf, "-- editing user %s --", SD_name);
	Send(buf, 0, false);
	snprintf(buf, USERBUFSIZ-1,     "- Full name: %s", SD_gecos);
	Send(buf, 0, false);
	if ((SD_email) != NULL)
	{
	    snprintf(buf, USERBUFSIZ-1, "- EMail at:  %s", SD_email);
	    Send(buf, 0, false);
	}
	sprintf(buf, "- Admin?:    %s",
		(SD_gid == room->getAdminGid() ? "YES" : "no" ));
	Send(buf, 0, false);
	if ((SD_sponsor) != NULL)
	{
	    snprintf(buf, USERBUFSIZ-1, "- Sponsor:   %s", SD_sponsor);
	    Send(buf, 0, false);
	}
	Send
	  ("- U-Username  P-Password  F-Full name  A-Admin?",0,false);
	Send
	  ("- S-Sponsor   E-EMail     Q-Quit",0,false);
	setAdmPrompt("  Admin: edit> ");
	break;
      }

    case ss_editgetuname:
      {
	SD_CHECK_VIRTUSERS_RUNNING;     /* Returns to main menu if not */
	setAdmPrompt("User to edit> ");
	break;
      }

    case ss_edituname:
      {
	setAdmPrompt(" New username> ");
	break;
      }

    case ss_editconfuname:
      {
	if  (0 == strlen(buf.buffer)) {
	  Send("No change.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	// TODO TODO TODO check for well-formed uname

	// store it temporarily into the unused pw_dir field...
	SD_dir = strdup(buf.buffer);
	if (NULL == SD_dir)
	{
	  room->Announce("L373: out of memory in ss_editconfuname", 0, false);
	  // gotta bail (user no longer has a pw)
	  admin_substate = ss_editmenu;  // so mainmenu handler frees fields
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	{
	  char ubuf[180];
	  sprintf(ubuf, "Really change %s's username to %s ? (Y/N) ",
		  SD_name, buf.buffer );
	  setAdmPrompt(ubuf);
	}
	break;
      }

    case ss_edituname_doit:
      {
	// make sure not logged in
	{
	  SLForumUser *maybe;
	  maybe = room->GetUserByName( SD_name );
	  if (maybe != NULL) {
	    Send
	      ("Error: Refusing to change a username while they're logged in.",
	       0, false);
	    free(SD_dir);   // cleanup
	    SD_dir = NULL;
	    setAdmSubstate(ss_editmenu);
	    return;
	  }
	}

	// stash the old name in an unused field (shell)
	SD_shell = SD_name;
	
	// change uname to new one
	SD_name = SD_dir;
	SD_dir  = NULL;

	// add new user :)
	slf_virtuser_put_info ( SD );

	// del old user
	if (! slf_virtuser_remove (SD_shell))
	{
	  Send("Could not find this user to rename them.", 0, false);
	  SD_name = SD_shell;
	  SD_shell = NULL;
	  free( SD_dir );
	  SD_dir = NULL;
	  setAdmSubstate(ss_editmenu);
	  return;
	}

	// cleanup
	free( SD_shell );   /* don't free SD_dir, it's in SD_name too! */
	SD_shell = NULL;
	SD_dir = NULL;
	setAdmSubstate(ss_editmenu);
	return;
      }

    case ss_editpw:
      {
	setAdmPrompt("        New password> ");
	break;
      }

    case ss_editconfpw:
      {
	if (strlen(buf.buffer) == 0)
	{
	  Send("Password change cancelled.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}

	if (strlen(buf.buffer) < 6)
	  Send("WARNING: password should be at least 6 chars long.", 0, false);
	
	/* copy it to statedata */
	// statedata is already used to hold the user info...
	// so where do we put the new possible password?
	// how bout the unused ->pw_dir.
	SD_dir = (char *) strdup(buf.buffer);
	if (SD_dir == NULL) {
	  room->Announce("L1810: Out of memory in ss_editconfpw handler");
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	/* set prompt and done */
	memset(buf.buffer, 0, sizeof(buf.buffer));
	setAdmPrompt("Confirm new password> ");
	break;
      }

    case ss_editpw_doit:
      {
	if (admin_substate != ss_editconfpw)
	  return;  /* TODO TODO TOOD some error msg? */

	/* compare it to statedata */
	int cmp = strcmp(buf.buffer, SD_dir);
	memset(buf.buffer, 0, sizeof(buf.buffer));
	if (0 != cmp)
	{
	  memset(SD_dir, 0, strlen(SD_dir));
	  free (SD_dir);
	  SD_dir = NULL;
	  Send("New passwords did not match.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	
	/* do it */
	const char *newpw;
	newpw = slf_cryptnewpw(SD_dir);
	memset(SD_dir, 0, strlen(SD_dir));
	free (SD_dir);
	SD_dir = NULL;

	// now store it back into the right field...
	free (SD_passwd);
	SD_passwd = strdup(newpw);
	if (NULL == SD_passwd)
	{
	  room->Announce("L373: out of memory in ss_editpw_doit", 0, false);
	  // gotta bail (user no longer has a valid pw)
	  admin_substate = ss_editmenu;  // so mainmenu handler will free data
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	slf_virtuser_put_info( SD );
	setAdmSubstate(ss_editmenu);
	return;
      }

    case ss_editgecos:
      {
	setAdmPrompt("New full name> ");
	break;
      }

    case ss_editgecos_doit:
      {
	if  (0 == strlen(buf.buffer)) {
	  Send("No change.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	// store it back into the right field...
	free (SD_gecos);
	SD_gecos = strdup(buf.buffer);
	if (NULL == SD_gecos)
	{
	  room->Announce("L373: out of memory in ss_editgecos_doit", 0, false);
	  // gotta bail (user no longer has a gecos)
	  admin_substate = ss_editmenu;  // so mainmenu handler will free data
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	slf_virtuser_put_info( SD );
	setAdmSubstate(ss_editmenu);
	return;
      }

    case ss_editsponsor:
      {
	setAdmPrompt("New sponsoring user> ");
	break;
      }

    case ss_editsponsor_doit:
      {
	if  (0 == strlen(buf.buffer)) {
	  Send("No change.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	// store it back into the right field...
	if ((SD_sponsor) != NULL) free ((char*)SD_sponsor);
	SD_sponsor = strdup(buf.buffer);
	if (NULL == SD_sponsor)
	{
	  room->Announce("L373: out of memory in ss_editsponsor_doit", 0, false);
	  // gotta bail (user no longer has a field)
	  admin_substate = ss_editmenu;  // so mainmenu handler will free data
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	slf_virtuser_put_info( SD );
	setAdmSubstate(ss_editmenu);
	return;
      }

    case ss_editemail:
      {
	setAdmPrompt("New EMail address> ");
	break;
      }

    case ss_editemail_doit:
      {
	if  (0 == strlen(buf.buffer)) {
	  Send("No change.", 0, false);
	  setAdmSubstate(ss_editmenu);
	  return;
	}
	// store it back into the right field...
	if ((SD_email) != NULL) free ((char*)SD_email);
	SD_email = strdup(buf.buffer);
	if (NULL == SD_email)
	{
	  room->Announce("L373: out of memory in ss_editemail_doit", 0, false);
	  // gotta bail (user no longer has a field)
	  admin_substate = ss_editmenu;  // so mainmenu handler will free data
	  setAdmSubstate(ss_mainmenu);
	  return;
	}
	slf_virtuser_put_info( SD );
	setAdmSubstate(ss_editmenu);
	return;
      }

    case ss_editisadmin:
      {
	setAdmPrompt("Admin? (Y/N) ");
	break;
      }




    ///////////// del user //////////////

   case ss_delgetuname:
     {
       SD_CHECK_VIRTUSERS_RUNNING;     /* Returns to main menu if not */
       setAdmPrompt("Enter name of user to delete> ");
       break;
     }

   case ss_dellookup:
     {
       // did they type anything?
       if (strlen(buf.buffer) == 0)
       {
	 Send("Delete cancelled.", 0, false);
	 setAdmSubstate(ss_mainmenu);
	 return;
       }

       // look up that user
       {
	 const struct slf_userinfo *arethey;
	 arethey = slf_virtuser_get_info(buf.buffer, NULL, false);
	 if (arethey == NULL)
	 {
	   Send("No user by that name was found.", 0, false);
	   setAdmSubstate(ss_mainmenu);
	   return;
	 }
       }
       statedata = strdup(buf.buffer);
       if (statedata == NULL) {
	 room->Announce("Assert failed: L303: out of mem during strdup");
	 setAdmSubstate(ss_mainmenu);
	 return;
       }
       // set prompt to confirm delete
       {
	 char surebuf[80];
	 sprintf(surebuf, "Really delete the user '%s' (y/n)? ", buf.buffer);
	 setAdmPrompt(surebuf);	 
       }
       room->logprintf("L585: %s about to del user %s\n", name, buf.buffer);
       setAdmSubstate(ss_delconfirm, true);
       return;
     }


   case ss_del_doit:
     {
       if (slf_virtuser_remove((char*)statedata)) /* statedata==name(string) */
	 {
	 Send("User deleted.", 0, false);
	 room->logprintf("L595: %s did del user %s\n", name, (char*)statedata);
	 }
       else {
	 Send("User could not be found.", 0, false);  // TODO error code chk?
	 room->logprintf("L599: %s couldn't del user %s, user not found\n", name, (char*)statedata);
       }

       free(statedata);
       setAdmSubstate(ss_mainmenu);
       return;
     }


    ///////////// list (not really done) //////////////

    case ss_list:
      {
	SD_CHECK_VIRTUSERS_RUNNING;     /* Returns to main menu if not */
	Send("List not yet implemented very well" ,0, false);
	{
	  const struct slf_userinfo *pw;
	  char fmtbuf[80];
	  gid_t admingid;
	  fmtbuf[79] = '\0';
	  admingid = room->getAdminGid();

	  pw = slf_virtuser_getfirst();
	  if (pw == NULL) {
	    Send("No virtual users in the database.", 0, false);
	  } else {
	    Send("Username  Full name", 0, false);
	  }

	  while (pw != NULL)
	  {
	    snprintf(fmtbuf, 79, "%-9s %-49s %s", 
		     pw->u.pw_name, pw->u.pw_gecos,
		     (pw->u.pw_gid == admingid ? "[Admin]" : "") );
	    Send(fmtbuf, 0, false);
	    pw = slf_virtuser_getnext();
	  }
	  slf_virtuser_getstop();
	}
	setAdmSubstate(ss_mainmenu);
	return;
      }

#undef SD_CHECK_VIRTUSERS_RUNNING  /* only needed above */

    ///////////// virtuser pw change //////////////

    case ss_pwch_getoldpw:
      {
	if (! is_virt) {
	  Send ("You cannot change your password within Shadowlands Forum.",
	     0, false);
	  setAdmSubstate(ss_nonadmin);
	  return;
	}
	Send("Changing password. (Nothing will be displayed.)", 0, false);
	room->logprintf("L654: %s about to change pw\n", name);
	setAdmPrompt("Your current password>");
	break;
      }

    case ss_pwch_getnewpw:
      {
	if (admin_substate != ss_pwch_getoldpw)
	  return;  /* TODO TODO TOOD some error msg? */	

	/* validate old pw */
	{
	  struct slf_userinfo *maybe=NULL;
	  maybe = validate(name, buf.buffer, longfrom);
	  if (maybe==NULL) {
	    Send("Sorry, password mistyped.", 0, false);
	    memset(buf.buffer, 0, sizeof(buf.buffer));
	    setAdmSubstate(ss_nonadmin);
	    return;
	  }
	}
	/* set prompt and done */
	memset(buf.buffer, 0, sizeof(buf.buffer));
	setAdmPrompt("         New password>");
	break;
      }

    case ss_pwch_confnewpw:
      {
	if (admin_substate != ss_pwch_getnewpw)
	  return;  /* TODO TODO TOOD some error msg? */

	if (0 == strlen( (char*) buf.buffer ))   // blank line (no pw entered)
	{
	    Send("Password change cancelled.", 0, false);
	    setAdmSubstate(ss_nonadmin);
	    return;
	}

	/* copy it to statedata */
	statedata = (void *) strdup(buf.buffer);
	if (statedata == NULL) {
	  room->Announce("L1810: Out of memory in ss_pwch_confnewpw handler");
	  setAdmSubstate(ss_nonadmin);
	  return;
	}
	/* set prompt and done */
	memset(buf.buffer, 0, sizeof(buf.buffer));
	setAdmPrompt(" Confirm new password>");
	break;
      }

    case ss_pwch_setnewpw:
      {
	if (admin_substate != ss_pwch_confnewpw)
	  return;  /* TODO TODO TOOD some error msg? */

	/* compare it to statedata */
	int cmp = strcmp(buf.buffer, (char *) statedata);
	memset(buf.buffer, 0, sizeof(buf.buffer));
	if (0 != cmp)
	{
	  memset(statedata, 0, strlen((char *) statedata));
	  free (statedata);
	  Send("New passwords did not match; please try again.", 0, false);
	  setAdmPrompt("         New password>");
	  setAdmSubstate(ss_pwch_getnewpw, true);
	  return;
	}
	
	/* do it */
	const char *newpw;
	newpw = slf_cryptnewpw((char *) statedata);
	memset(statedata, 0, strlen((char *) statedata));
	free (statedata);
	room->logprintf("L723: %s has changed pw, storing it back to db\n", name);

	/* store it back to db */
	{
	  struct slf_userinfo *pw;
	  pw = slf_virtuser_get_info(name, NULL, false);
	  if (pw == NULL) {
	    room->Announce("Assert failed: L1874: virtuser no longer in db");
	  } else {
	    pw->u.pw_passwd = (char *) newpw;
	    slf_virtuser_put_info(pw);
	  }
	}

	/* set prompt and done */
	memset(buf.buffer, 0, sizeof(buf.buffer));
	setAdmSubstate(ss_nonadmin);
	Send("Password change successful.", 0, false);
	return;
      }



    ///////////// macros //////////////

#ifdef ALLOW_MACROS

    case ss_macro_expand_readkey:
      {
	  admin_substate = ss_macro_expand_readkey;
	  // above line makes sure all looks right when input line redisplayed
	  setAdmPrompt("USE MACRO> ", false);  break;
      }

    case ss_macro_addedit_readkey:
      {
	  Send("Type the key to create or edit the macro for.", 0, false);
	  setAdmPrompt("CREATE/EDIT MACRO>");  break;
      }

    case ss_macro_addedit_checkkey:
      {
	  char mc;    // macro character (key)
	  macro_t *newmac;  // new mac structure
	  char* macdata;    // buffer for macro's expanded value

	  if (admin_substate == ss_macro_addedit_readkey)
	  {
	      // key press is stored in state_d_char
	      mc = state_d_char;

	      // does it exist?
	      LinkedListLink *tryfind = tryFindMacro(mc);
	      if (tryfind != NULL)
	      {
		  Send ("A macro with this key already exists.", 0, false);
		  statedata = llist_ldata(tryfind);
		  setAdmSubstate(ss_macro_addedit_askreplace);
		  return;
	      }

	      // set up for it
	      newmac = (macro_t *) malloc(sizeof(macro_t));
	      if (newmac == NULL)
	      {
		  room->Announce
		      ("-- out of mem trying to make macro struct --");
		  setAdmSubstate(ss_nonadmin);
		  return;
	      }

	      macdata = (char*) calloc (MAX_MACRO_LENGTH, sizeof(char));
	      if (macdata == NULL)
	      {
		  room->Announce
		      ("-- out of mem trying to hold macro buffer --");
		  free(newmac);
		  setAdmSubstate(ss_nonadmin);
		  return;
	      }

	      statedata   = newmac;   // remember it
	      newmac->key = mc;       // incl its key :)
	      newmac->val = macdata;  // and the buffer for its value
	      newmac->editing = false;  // new macro, not an edit
	                    // (this is important when we go to save it.)

	  } else if (admin_substate == ss_macro_addedit_askreplace)
	  {
	      // replacing an old one.

	      newmac  = (macro_t *) statedata;
	      macdata = (char *) newmac->val;  // cast cuz no longer const
	      mc      = newmac->key;
	      newmac->editing = true;    // so nobody tries to use it... :)
	  } else
	  {
	      room->Announce ("L830: Assert failed: bad substate going in.");
	      room->logprintf("L830: going in was %d\n", (int) admin_substate);
	      setAdmSubstate(ss_nonadmin);
	      return;
	  }
	  statedat2   = macdata;  // insert point at head of buffer

	  {
	      char namebuf[64];
	      snprintf(namebuf, 64, "Creating macro for ^E %s",
		       keyDisplayString(mc));
	      Send(namebuf, 0, false);
	  }

	  Send("Type the macro; type a blank line to finish.", 0, false);
	  Send("To enter control characters, precede them with Ctrl-Q.",
	       HINT_MACRO_CTRLQ, 0, 0);
	  setAdmSubstate(ss_macro_addedit_readline);
	  return;
      }

    case ss_macro_addedit_askreplace:
      {
	  setAdmPrompt("Replace it? (y/N)");
	  break;
      }

    case ss_macro_addedit_readline:
      {
	  // clear input buffer iff we're not leaving ctrl-q mode
	  // TODO TODO TODO find bug or not, and remove this debug print
	  // room->logprintf ("L979: running_cmd is %d\n", (int) running_cmd);
	  setAdmPrompt("Expand to> ",
		       admin_substate != ss_macro_addedit_ctrlq);
	  break;
      }

    case ss_macro_addedit_checkline:
      {
	  // either add it to the macro in progress, or (if the
	  // line's blank) they're done entering that macro.

	  if (buf.cur_len > 0)
	  {
	      macro_append_line();
	      setAdmSubstate(ss_macro_addedit_readline); // incl redisplaying
	      return;                                    // the prompt
	  } else {
	      setAdmSubstate(ss_macro_addedit_askcrlf);
	      return;
	  }
      }

    case ss_macro_addedit_ctrlq:
	{
	    setAdmPrompt ("^Q    >", false);
	    break;
	}

    case ss_macro_addedit_askcrlf:
	{
	    setAdmPrompt("Should Forum hit enter at the end of this macro? ");
	    break;
	}

    case ss_macro_addedit_doit:
	{
	    // k, done with that macro, let's add it
	    *((char *) statedat2) = '\0';  // mark end of macro
	    statedat2 = NULL;
	    if ( ! macro_editing(statedata) )
	    {
		if (! tryAddMacro ( (macro_t *) statedata ))
		    Send("Could not add macro.", 0, false);
		else
		{
		    macro_editing(statedata) = false;
		    savePrefs();
		}
	    } else
	    {
		macro_editing(statedata) = false;
		savePrefs();
	    }
	    statedata = NULL;

	    setAdmSubstate(ss_nonadmin);
	    return;
	}

    case ss_macro_delete_readkey:
	{
	    Send ("Type the key of the macro to be deleted.", 0, false);
	    setAdmPrompt ("Delete Macro>");
	    break;
	}

    case ss_macro_delete_confirm:
	{
	    char surebuf[80];
	    sprintf(surebuf, "Really delete the macro for ^E %s (y/N)? ",
		    keyDisplayString ( state_d_char, true) );
	    setAdmPrompt(surebuf);
	    break;
	}

#endif  /* def (ALLOW_MACROS) */


    case ss_idlesoon_hitanykey:
	{
	    if (admin_substate == ss_nonadmin)
	    {
		    setAdmPrompt ("Hit any key>", false);
		    break;
	    }  // too confusing for user from any other substate
	}

    ///////////// anything else? //////////////

    default:
      room->Announce ("L446: unknown new admin substate");
      room->logprintf("L446: cur substate is %d, attempted new is %d\n",
		      (int) admin_substate, (int) subst);
      return;
    }  // endsw (subst)

  } else {  // (! justSetState)

    if ((subst < ss_nonadmin) || (subst >= SS_NUM_SUBSTATES))
    {
      room->Announce ("L446: unknown new admin substate");
      room->logprintf("L446: cur substate is %d, attempted new is %d\n",
		      (int) admin_substate, (int) subst);
      return;
    }
    
  }  // end if (! justSetState)

  if(subst != admin_substate)
  {
    admin_prev_substate = admin_substate;
    admin_substate = subst;
  }
  if (admin_substate == ss_nonadmin)   // clear buffer when leaving admin subst
  {
      if ((admin_prev_substate != ss_macro_expand_readkey)
	  && (admin_prev_substate != ss_idlesoon_hitanykey))
      {
	  setAdmPrompt(NULL);    // standard: clear buffer and prompt
      }
      else        // from expand_readkey or idlesoon_hitanykey: keep buffer
      {
	  // set show_input, macro_input properly (since we're about to
	  // redisplay the entry line):                         (20020728.1140)
	  show_input = admInputSSBehavTable[(int)admin_substate].show_input;
	  buf.macro_input = (admin_substate == ss_macro_addedit_readline);

	  // display:
	  setAdmPrompt(NULL, false);  
      }
  }

  show_input = admInputSSBehavTable[(int)admin_substate].show_input;
  buf.macro_input = (admin_substate == ss_macro_addedit_readline);

}  // setAdmSubstate



admin_substate_t SLForumUser::getAdmSubstate() const
{
	return admin_substate;
}






/** Read a key in one of the substates, and act accordingly.
 *  Returns status int to ManageUser (like HandleGetLine, HandleTelCmd, etc):
 *  Return 0 to continue connection, -1 if user's disconnected.
 */
int	SLForumUser::HandleAdminLine()
{
  /** get a key */
  char c;
  int ret = slfgetch(&c);
  if (ret<1)
      return ret;   // no input, or errors
  if (c == 0)
      return 0;

  if (admin_substate < ss_notadmin_FIRST)  // when in any admin menu substate,
  {                                        // cmd char escapes temporarily,
                                           // so check for cmd char. (at start
                                           // of line only)

      /* This condition MUST be equal to that at the code in
       * user::RedisplayEntryLine; that's where the appropriate prompt
       * must be chosen and redisplayed.
       * Search for 20020706.0930 to find the code.
       */

      /** if it's manageinputbuffer mode, manage it */
      if ((c == pref.cmd_char_alt) && (buf.cur_len == 0))
	  c = pref.cmd_char;  // substitute real one (20000815.1630)

      if ((c == pref.cmd_char) && (buf.cur_len == 0))
      {
	  // entering escaped mode
	  // (entering a 1-line SLForum command,
	  //  not a 1-letter admin command)
	  show_input = true;
	  ret = ManageInputNewChar(c);  /* insert it into buffer */
	  if (ret<0)
	      return ret;
	  promptlen = realpromptlen;          /* set prompt length, */
	  RedisplayEntryLine();   /* redisplay with user, not admin, prompt */
	  return ret;
      } else {
	  // already in escaped mode, type new char into the buffer
	  if (buf.buffer[0] == pref.cmd_char)
	  {
	      ret = ManageInputNewChar(c);
	      if (ret < 0)
		  return ret;

	      // handle changed buffer
	      ret = ManageChangedInputBuffer(ret);
	      return ret;
	  }
      }
  }  // end check for cmd char

  /** if we get here, we're not in escaped mode */

  switch (admin_substate)
  {
  case ss_mainmenu:
    c = tolower(c);
    switch(c)
    {
    case 'a':  setAdmSubstate(ss_addgetuname);   break;
    case 'l':  setAdmSubstate(ss_list);          break;
    case 'e':  setAdmSubstate(ss_editgetuname);  break;
    case 'd':  setAdmSubstate(ss_delgetuname);   break;
    case 'q':  setAdmSubstate(ss_nonadmin);      break;
    default :  {}  /* ignore. */
    }
    break;



  case ss_addaskadmin:
    c = tolower(c);
    if ((c == 'y') || (c == (char)13)) {
      setAdmPrompt("Adding user...");
      Send("           admin> Yes");	// TODO SLF_PROMPT_CHAR
      SD_gid = room->getAdminGid();
      setAdmSubstate(ss_add_doit);
    } else if (c == 'n') {
      setAdmPrompt("Adding user...");
      Send("           admin> No");	// TODO SLF_PROMPT_CHAR
      SD_gid = room->getUsersGid();
      setAdmSubstate(ss_add_doit);
    }  // else ignore key
    break;


  case ss_editmenu:
    c = tolower(c);
    switch(c)
    {
    case 'u':  setAdmSubstate(ss_edituname);    break;
    case 'p':  setAdmSubstate(ss_editpw);       break;
    case 'f':  setAdmSubstate(ss_editgecos);    break;
    case 'a':  setAdmSubstate(ss_editisadmin);  break;
    case 's':  setAdmSubstate(ss_editsponsor);  break;
    case 'e':  setAdmSubstate(ss_editemail);    break;
    case 'q':  setAdmSubstate(ss_mainmenu);     break;	
    default :  {}  /* ignore. */
    }  /* switch(c) */
    break;  /* case */


  case ss_editconfuname:
    c = tolower(c);
    if ((c == 'y') || (c == (char)13)) {
      Send("** Changing username. **", 0, false);
      setAdmSubstate(ss_edituname_doit);
    } else if ((c == 'n') || (c == (char)27)) {
      Send("** Cancelled! **", 0, false);
      free(SD_dir);
      SD_dir = NULL;
      setAdmSubstate(ss_editmenu);
    } 
    break;


  case ss_editisadmin:
    c = tolower(c);
    if ((c == 'y') || (c == (char)13)) {
      Send("               Admin> Yes");	// TODO SLF_PROMPT_CHAR
      SD_gid = room->getAdminGid();
      slf_virtuser_put_info(SD);
      setAdmSubstate(ss_editmenu);
    } else if ((c == 'n') || (c == (char) 27)) {
      Send("               Admin> No");		// TODO SLF_PROMPT_CHAR
      SD_gid = room->getUsersGid();
      slf_virtuser_put_info(SD);
      setAdmSubstate(ss_editmenu);
    }
    break;


  case ss_delconfirm:
    c = tolower(c);
    if ((c == 'y') || (c == (char)13)) {
      setAdmSubstate(ss_del_doit);
    } else if ((c == 'n') || (c == (char)27)) {
      Send("Deletion cancelled.", 0, false);
      setAdmSubstate(ss_mainmenu);
    }
    break;


    ///////////// macros //////////////

#ifdef ALLOW_MACROS

  case ss_macro_expand_readkey:
      setAdmSubstate(ss_nonadmin);
      return tryExpandMacro(c, true);  // also checks for ^E^H, ^E<esc>

  case ss_macro_addedit_readkey:
      if (c == 27)
      {
	  Send("Macro add/edit cancelled.", 0, false);
	  setAdmSubstate(ss_nonadmin);
      } else if (c == 8)
      {
	  Send("Macro add/edit cancelled - cannot create ^H macro.", 0, false);
	  setAdmSubstate(ss_nonadmin);
      } else {
	  state_d_char = c;   // remember keypress
	  setAdmSubstate(ss_macro_addedit_checkkey);
      }
      break;

  case ss_macro_addedit_askreplace:
    c = tolower(c);
    if (c == 'y')
    {
	Send("   Replace> Yes");	// TODO SLF_PROMPT_CHAR
	setAdmSubstate(ss_macro_addedit_checkkey);
    } else
    {
	Send("   Replace> No");		// TODO SLF_PROMPT_CHAR
	setAdmSubstate(ss_nonadmin);
    }
    break;

  case ss_macro_addedit_ctrlq:
      buf.buffer[buf.cur_pos] = c;    // add the 'quoted' char to buffer
      ++buf.cur_pos;
      buf.buffer[buf.cur_pos] = '\0';
      {
	  const char *mcs;
	  mcs = keyDisplayString(c);
	  buf.cur_len += strlen(mcs);     // update cur_len
	  uWrite ( mcs );     // echo it out
      }
      setAdmSubstate (ss_macro_addedit_readline, false);  // back to readline
      break;

  case ss_macro_addedit_askcrlf:
      c = tolower(c);
      if ((c == 'y') || (c == (char)13)) {
	  Send("Hit enter after macro playback: Yes");
	  macro_crlf(statedata) = true;
      } else {
	  Send("Hit enter after macro playback: No");
	  macro_crlf(statedata) = false;
      }
      setAdmSubstate(ss_macro_addedit_doit);
      break;

  case ss_macro_delete_readkey:
      if (NULL == tryFindMacro (c))
      {
	  Send ("No macro with that key exists.", 0, false);
	  setAdmSubstate (ss_nonadmin);
      } else
      {
	  state_d_char = c;
	  setAdmSubstate (ss_macro_delete_confirm);
      }
      break;

  case ss_macro_delete_confirm:
      c = tolower(c);
      if (c == 'y') {
	  if (tryDelMacro ( state_d_char ))
	  {
	      Send("Macro deleted.", 0, false);
	      savePrefs();
	  } else {
	      Send("Could not delete the macro.", 0, false);
	  }
      }
      else
      {
	  Send("Macro delete cancelled.", 0, false);
      }
      setAdmSubstate (ss_nonadmin);
      break;

#endif  /* def (ALLOW_MACROS) */


  case ss_idlesoon_hitanykey:
      this->lastkey = this->lastping;           // reset idle time
      setAdmSubstate (ss_nonadmin);             // back to normal mode
      ret = ManageInputNewChar (c);             // handle that keypress
      if (ret < 0)
	      return ret;
      break;

    ///////////// anything else - input states //////////////

  default:
    /* try to find it in the admInputSubstateBehavTable */
    if ((admin_substate < ss_nonadmin) || (admin_substate >= SS_NUM_SUBSTATES))
    {
      /* out of range */

      room->Announce("L591: Assert failed: Unknown admin substate!");
      room->logprintf("L591: unknown substate is %d\n", (int) admin_substate);
      return 0;
    }

    /* this is the code for any input substate */

    const admin_input_substate_behav_t* behav;
    behav = &(admInputSSBehavTable[(int)admin_substate]);

    if (behav->nextstate == ss_unused)
    {
	room->Announce("L1109: Assert failed: No handler for substate!");
	room->logprintf("L1109: substate is %d\n", (int) admin_substate);
	return 0;
    }

    ret = ManageInputNewChar (c);
    if (ret < 0)
      return ret;
    if (ret == 1) {
      // user hit return; thus, time for substate transition
      uWrite("\r\n");  // retain it on screen
      setAdmSubstate (behav->nextstate);
    }
    return 0;

  }  // endsw

  return 0;

}  /* handleadminline */



#undef SD
#undef SD_name
#undef SD_passwd
#undef SD_gid
#undef SD_gecos
#undef SD_dir
#undef SD_shell





////////////////////////////////////////
// end include
// slforumuser-adminsubstate.inc.cc
// for SLForumUser
//
////////////////////////////////////////
