From: jis The members you are adding will receive email sent to this list
+within 24 hours. If you are adding members to an Athena group with access to files in AFS, they
+immediately have access to those files. You will begin receiving email from this list within 24 hours. If
+you have added yourself to an Athena group with access to files in AFS, you
+will have immediate access to those files. List owners may add list members of three common types: Select the type of member you wish to add, and enter the member(s)
+you wish to add. You may enter more than one member of the same
+type by listing each member on its own line. The members you are deleting will stop receiving email from this
+list within 24 hours. If you have removed members from an Athena group with access to files in AFS, they no
+longer have access to those files. You will stop receiving email from this list within 24 hours. The members you are deleting will stop receiving email from this
+list within 24 hours. If you are removing members from an Athena group with access to files in AFS, they
+will no longer have access to those files. An Athena list has a set of characteristics that describe the
+list's functionality and purpose. The list's owner can set many of the
+list's characteristics. The characteristics include
+
+description,
+
+list type (mailing list, group),
+
+administrator,
+
+list permissions (active,
+inactive, public, private, visible, hidden),
+
+and last modification.
+
+ MIT community members may add themselves to any public Athena list
+and remove themselves from any Athena lists. Below you will see if you are currently a member of a list, and be
+prompted to change your membership. Be aware that Athena lists can contain other Athena mailing lists.
+If you are not a member of a list directly, you may still be on the
+list through another list that is a member (called a sublist). If
+this is the case, you can use the show list members function to
+see what sublists are members of this list. The members of this list are displayed with their type. List members
+can be of four different types:
+
+ MIT community members may request
+ a list from Athena User Accounts for MIT-specific purposes. Lists
+ may be used to distribute messages to multiple recipients, to share files
+ in a locker, or to serve as an email alias for a particular user. Lists
+ must be owned by a single user or another list. Course mailing lists should be requested using the ACS Course Mailing List Request
+ Form. How to
+Manage your Athena Lists | Who to Contact for
+Help | Glossary of List Management
+Terms How to Manage your Athena
+Lists Athena List Management is one of the tools list owners have to
+manage their lists. Other tools include blanche and listmaint on
+Athena. To get started, you will need MIT Personal Certificates on
+your machine. Once you have them, select any function, provide a list
+name, and press enter on your keyboard, or 'Go'. Each page of the web
+interface has context sensitive help to guide you. [top of help] If you do need further assistance with your lists, Athena User Accounts provides
+assistance with Athena list concerns, including creating new lists,
+modifying lists, and deleting lists. If you have trouble using this
+interface to maintain your lists, please contact Accounts at accounts@mit.edu, or
+617-253-1325. [top of
+help] Glossary of List
+Management Terms List Description Mailing List Group Note that only Athena users on your list will be able to take
+advantage of this feature. If you have any other members on your list,
+such as email addresses outside of MIT, they will not be able to
+access Athena file systems. [top of
+help] List Administrator(s) List Permissions Last Modification Athena User List List members can be other Athena lists, of the form
+listname@mit.edu. To add an Athena list as a member of another Athena
+list, enter only the part of the address before the @ symbol. For
+example, to add accounts@mit.edu as a member, provide 'accounts' as
+the member name. [top of
+help] String Kerberos Principal List owners may delete any list members. If you wish to have a list
+deleted, please contact Athena
+Accounts. Check each member you wish to delete, and then 'Delete
+Selected'. For list with many members, you will see initially see ten
+members per screen, and will be able to move to the next or previous
+ten members, selecting as many members to delete as you wish. If you
+would prefer to see all the members of a list, select 'Show All' to
+display all list members. Please note that displaying all members at
+once may take a long time to load. Welcome to the Athena list management web
+interface. This is one of several
+tools you can use to update list membership information and
+characteristics. This service requires MIT Personal
+ Certificates. To begin,
+
+
+
+
+
+
+
+
+
+
+
A short
+description may be specified for an Athena list. This description is
+displayed for lists which are not marked hidden if list information is
+generated by another Athena user. [top of help]
An Athena list is by
+default a mailing list. That means that the members of the list will
+be mailable through the address listname@mit.edu, which will
+distribute the mail the all the members of the list. Such a list may
+include email addresses for users outside of MIT. Lists can be both a
+mailing list and a group. [top of
+help]
In addition to (or instead of)
+being a mailing list, an Athena list can also be a group. A group can
+be used as an access control list on the AFS file system, for
+example. If you wish to be able to set access permissions on an Athena
+directory or locker for the members of your list, you should choose to
+make it a group. When a list can maintain a file space on Athena, it
+is also has a group ID number.
Every Athena
+mailing list must be owned by one or more
+administrators. Administrators have the power to change list
+characteristics. Administrators can be a user or another Athena
+list. [top of help]
These
+describe the state of the list and what users who are not the
+administrator can do with the list. A list's permissions can be viewed
+with the "display list characteristics" option, and edited
+by list owners with the "update list characteristics"
+option.
+
+
+[top of help]
+
+
This tells you
+when the list was last changed in some way and by who. [top of help]
Email addresses of the
+form name@mit.edu usually indicate name is an Athena username (also
+sometimes called a Kerberos Name or MIT Network ID). To add an Athena
+user to an Athena list, you enter only member name to the left of the
+@. For example, to add eclapton@mit.edu to an Athena list, provide
+'eclapton' as the member name. [top
+of help]
An Athena list is by default a
+mailing list. That means that the members of the list will be mailable
+through the address listname@mit.edu, which will distribute the mail
+the all the members of the list. Such a list may include email
+addresses for users outside of MIT.
Athena lists often have
+non-MIT users and lists as members. To add an email address that
+contains an @ but is outside of mit.edu, such as
+mitalum@anotherisp.net, you add them as member type string. To add a
+string, provide the complete address, including information on both
+sides of the @. [top of
+help]
+Specifying a list member as a Kerberos principal allows a member to be
+on a list without receiving any email sent to the list. This is a
+rarely used member type. [top of
+help].
+
Feedback and questions about this service may be directed to web-listmaint@mit.edu.
+ + diff --git a/webmoira/fileparts/updatelistinfo.html b/webmoira/fileparts/updatelistinfo.html new file mode 100644 index 00000000..ad9ff575 --- /dev/null +++ b/webmoira/fileparts/updatelistinfo.html @@ -0,0 +1,9 @@ +An Athena list has a set of characteristics that describe the +list's functionality and purpose. The list's owner may update many of +the list's characteristics below, including if the list is a mailing list, a public or private list, and if it is +a hidden or visible +list. You may also edit the list administrator and description. diff --git a/webmoira/fileparts/updatelistinfoconf.html b/webmoira/fileparts/updatelistinfoconf.html new file mode 100644 index 00000000..b80883be --- /dev/null +++ b/webmoira/fileparts/updatelistinfoconf.html @@ -0,0 +1 @@ +
Updates to list characteristics will take effect within 24 hours.
diff --git a/webmoira/help.jhtml b/webmoira/help.jhtml new file mode 100644 index 00000000..584e547f --- /dev/null +++ b/webmoira/help.jhtml @@ -0,0 +1,107 @@ + + + + ++ list + management services: help | +|||||||||||||||||
+ +
|
+
+ |
+ ||||||||||||||||
+ |
+ + diff --git a/webmoira/home.gif b/webmoira/home.gif new file mode 100644 index 00000000..9e5cac4c Binary files /dev/null and b/webmoira/home.gif differ diff --git a/webmoira/index.jhtml b/webmoira/index.jhtml new file mode 100644 index 00000000..ed3eebea --- /dev/null +++ b/webmoira/index.jhtml @@ -0,0 +1,109 @@ +
+ athena list + management | +|||||||||||||||||
+ +
help + + + + |
+
+ + + + |
+
+ + diff --git a/webmoira/mit.gif b/webmoira/mit.gif new file mode 100644 index 00000000..68932712 Binary files /dev/null and b/webmoira/mit.gif differ diff --git a/webmoira/mit/moira/AuthenticationError.java b/webmoira/mit/moira/AuthenticationError.java new file mode 100644 index 00000000..43630e86 --- /dev/null +++ b/webmoira/mit/moira/AuthenticationError.java @@ -0,0 +1,8 @@ +package mit.moira; + +public class AuthenticationError extends Exception { + public AuthenticationError(String message) { + super(message); + } + +} diff --git a/webmoira/mit/moira/Coder.java b/webmoira/mit/moira/Coder.java new file mode 100644 index 00000000..8ac18193 --- /dev/null +++ b/webmoira/mit/moira/Coder.java @@ -0,0 +1,146 @@ +package mit.moira; + +import java.util.Vector; +import java.util.Enumeration; + +/** + * Class with static functions for base64 encoding and decoding as well + * as other utilities. + */ + +public class Coder { + static final char encv[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; + static char x2c (char [] what, int off) { + int digit; + digit = (what[off] >= 'A' ? ((what[off] & 0xdf) - 'A')+10 : (what[off] - '0')); + digit *= 16; + digit += (what[off+1] >= 'A' ? ((what[off+1] & 0xdf) - 'A')+10 : (what[off+1] - '0')); + return((char)digit); + } + + private Coder() { // No Constructors... + } + + /** + * Remove URL escape sequences + * + * @param aurl A String with potential embedded escapes. + * @return the String with the escapes removed. + */ + public static String unescape_url(String aurl) { + int x, y, i; + char [] url = new char [aurl.length()]; + aurl.getChars(0, aurl.length(), url, 0); + StringBuffer ret = new StringBuffer(); + for (i = 0; i < url.length; i++) { + if (url[i] == '%') { + ret.append(x2c(url, i+1)); + i += 2; + } else if (url[i] == '+') { + ret.append(' '); + } else ret.append(url[i]); + } + return (new String(ret)); + } + + /** + * Encodes a byte array in base64 characters and returns the result + * as a String. + * + * @param data the Byte array + * @return The encoded string + */ + + public static String encode(byte [] data) { + StringBuffer ret = new StringBuffer(); + int len = data.length; + int c; + int p = 0; + int i = 0; + int nib1, nib2, nib3, nib4; + while (i < len) { + if ((i+3) <= len) { + c = (data[i++] & 0xFF); + c = (c << 8) | (data[i++] & 0xFF); + c = (c << 8) | (data[i++] & 0xFF); + nib1 = (c & 0x0FC0000) >>> 18; + nib2 = (c & 0x003F000) >>> 12; + nib3 = (c & 0x0000FC0) >>> 6; + nib4 = (c & 0x000003F); + ret.append(encv[nib1]); + ret.append(encv[nib2]); + ret.append(encv[nib3]); + ret.append(encv[nib4]); + } else { + p = len - i; + c = data[i++]; + c = c << 8; + if (p > 1) c |= (data[i++] & 0xFF); + c = c << 8; + if (p > 2) c |= (data[i++] & 0xFF); // Should never happen + nib1 = (c & 0x0FC0000) >>> 18; + nib2 = (c & 0x003F000) >>> 12; + nib3 = (c & 0x0000FC0) >>> 6; + nib4 = (c & 0x000003F); + ret.append(encv[nib1]); + ret.append(encv[nib2]); + if (p == 1) ret.append('='); + else ret.append(encv[nib3]); + ret.append('='); + } + } + return (new String(ret)); + } + + /** + * Decodes a base64 encoded string and returns the decoded value + * in a byte array. + * + * @param s The base64 encoded string + * @return the byte array decoded + */ + public static byte [] decode (String s) { + Vector v = new Vector(); + int p = 0; + int i = 0; + int k = 0; + int z = 0; + int reg = 0; + for (i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '/') z = 63; + else if (c == '+') z = 62; + else if ((c >= 'a') && (c <= 'z')) z = (c - 'a') + 26; + else if ((c >= 'A') && (c <= 'Z')) z = (c - 'A'); + else if ((c >= '0') && (c <= '9')) z = (c - '0') + 52; + else if (c == '=') { + p++; + z = 0; + } else continue; + reg <<= 6; + reg += z; + k++; + if (k == 4) { + for (z = 0; z < 3; z++) { + v.addElement(new Integer((reg & 0xff0000) >>> 16)); + reg = (reg << 8); + } + k = 0; + } + } + Enumeration e = v.elements(); + byte [] retval = new byte[v.size() - p]; + for(i = 0; i < v.size() - p; i++) { + retval[i] = ((Integer) e.nextElement()).byteValue(); + } + return (retval); + } +} + + + diff --git a/webmoira/mit/moira/Kticket.java b/webmoira/mit/moira/Kticket.java new file mode 100644 index 00000000..28c6f445 --- /dev/null +++ b/webmoira/mit/moira/Kticket.java @@ -0,0 +1,64 @@ +package mit.moira; + +import java.util.Date; +import java.io.IOException; + +public class Kticket implements Runnable { + Object LOCK; + String name; + String instance; + String realm; + boolean dostop = false; + Runtime r; + Date renewTime; + + Kticket(String name, String instance, String realm, Object lock) { + this.name = name; + this.instance = instance; + this.realm = realm; + this.LOCK = lock; + r = Runtime.getRuntime(); + renewTime = new Date(); + } + + public void run() { + for(;;) { + if (dostop) return; + Date now = new Date(); + if (now.after(renewTime)) { + renew(); + renewTime = new Date(System.currentTimeMillis() + 6*3600*1000L); // 6 hours + } + try { + Thread.sleep(30*1000L); // Sleep for 30 seconds + } catch (InterruptedException i) { + // Nothing to be done about it + } + } + } + + public void renew() { + synchronized(LOCK) { + try { + Process p = r.exec("/usr/athena/bin/kinit -k -t /mit/jis/javahacking/moira/KEY " + name + "/" + instance + "@" + realm); + p.waitFor(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException i) { + } + } + } + + public void destroy() { + dostop = true; + synchronized(LOCK) { + try { + Process p = r.exec("/usr/athena/bin/kdestroy"); + p.waitFor(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException i) { + } + } + } +} diff --git a/webmoira/mit/moira/ListInfo.java b/webmoira/mit/moira/ListInfo.java new file mode 100644 index 00000000..34b8ee7b --- /dev/null +++ b/webmoira/mit/moira/ListInfo.java @@ -0,0 +1,48 @@ +package mit.moira; + +import java.util.Date; +import java.text.SimpleDateFormat; + +public class ListInfo { + String name; + boolean active; + boolean bpublic; + boolean hidden; + boolean maillist; + boolean grouplist; + String gid_original; + int gid; + String ace_type; + String ace_name; + String description; + Date modtime; + String moduser; + String modwith; + static final SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss"); + + public ListInfo(String name, String active, String spublic, String hidden, String maillist, String grouplist, String gid, String ace_type, String ace_name, String description, String modtime, String moduser, String modwith) throws MoiraException { + this.name = name; + this.active = (active.equals("1")) ? true : false; + this.bpublic = (spublic.equals("1")) ? true : false; + this.hidden = (hidden.equals("1")) ? true : false; + this.maillist = (maillist.equals("1")) ? true : false; + this.grouplist = (grouplist.equals("1")) ? true : false; + this.gid_original = gid; + try { + this.gid = Integer.parseInt(gid); + } catch (NumberFormatException e) { + this.gid = 0; + } + this.ace_type = ace_type; + this.ace_name = ace_name; + this.description = description; + try { + this.modtime = df.parse(modtime); + } catch (java.text.ParseException p) { + throw new MoiraException(p.getMessage()); + } + this.moduser = moduser; + this.modwith = modwith; + } +} + diff --git a/webmoira/mit/moira/Member.java b/webmoira/mit/moira/Member.java new file mode 100644 index 00000000..5bbf51d3 --- /dev/null +++ b/webmoira/mit/moira/Member.java @@ -0,0 +1,23 @@ +package mit.moira; + +public class Member { + String member_type; + String member_id; + + public Member(String mt, String mi) { + member_type = mt; + member_id = mi; + } + + public String getMemberType() { + return (member_type); + } + + public String getMemberId() { + return (member_id); + } + + public String toString() { + return("[Member type=" + member_type + ", id=" + member_id + "]"); + } +} diff --git a/webmoira/mit/moira/MoiraConnect.java b/webmoira/mit/moira/MoiraConnect.java new file mode 100644 index 00000000..d34d1856 --- /dev/null +++ b/webmoira/mit/moira/MoiraConnect.java @@ -0,0 +1,119 @@ +package mit.moira; + +public class MoiraConnect { + boolean connected = false; + boolean isauth = false; + Object LOCK; + + String server = ""; + MoiraConnect() { + } + + public MoiraConnect(String server, Object lock) { + this.server = server; + this.LOCK = lock; + } + + public void connect() throws MoiraException { + if (!connected) { + MoiraConnectInternal.connect(server); + connected = true; + } + } + + public void auth() throws MoiraException { + if (!isauth) { + synchronized(LOCK) { + MoiraConnectInternal.auth(); + } + isauth = true; + } + } + + public void proxy(String user) throws MoiraException { + MoiraConnectInternal.proxy(user); + } + + public void disconnect() { + if (connected) { + MoiraConnectInternal.disconnect(); + connected = false; + isauth = false; + } + } + + public ListInfo get_list_info(String list) throws MoiraException { + if (!connected) throw new MoiraException("Not Connected"); + String [] args = new String[1]; + args[0] = list; + Object [] retinternal = MoiraConnectInternal.mr_query("get_list_info", args); + if (retinternal == null) return (null); + if (retinternal.length == 0) return (null); + if (retinternal.length != 1) throw new MoiraException("get_list_info returned more then one list!"); + String [] entry = (String []) retinternal[0]; + return (new ListInfo(entry[0], entry[1], entry[2], entry[3], entry[4], entry[5], entry[6], entry[7], entry[8], entry[9], entry[10], entry[11], entry[12])); + } + + void update_list_info(String list, ListInfo info) throws MoiraException { + if (!connected) throw new MoiraException("Not Connected"); + String [] args = new String[11]; + args[0] = list; + args[1] = info.name; + args[2] = info.active ? "1" : "0"; + args[3] = info.bpublic ? "1" : "0"; + args[4] = info.hidden ? "1" : "0"; + args[5] = info.maillist ? "1" : "0"; + args[6] = info.grouplist ? "1" : "0"; + args[7] = info.gid_original; + args[8] = info.ace_type; + args[9] = info.ace_name; + args[10] = info.description; + Object [] retinternal = MoiraConnectInternal.mr_query("update_list", args); + return; + } + + void delete_member_from_list(String list, String type, String member) throws MoiraException { + if (!connected) throw new MoiraException("Not Connected"); + String [] args = new String[3]; + args[0] = list; + args[1] = type; + args[2] = member; + Object [] retinternal = MoiraConnectInternal.mr_query("delete_member_from_list", args); + return; + } + + void add_member_to_list(String list, String type, String member) throws MoiraException { + if (!connected) throw new MoiraException("Not Connected"); + String [] args = new String[3]; + args[0] = list; + args[1] = type; + args[2] = member; + Object [] retinternal = MoiraConnectInternal.mr_query("add_member_to_list", args); + return; + } + + public Member [] get_members_of_list(String list) throws MoiraException { + if (!connected) throw new MoiraException("Not Connected"); + String [] args = new String[1]; + args[0] = list; + Object [] retinternal = MoiraConnectInternal.mr_query("get_members_of_list", args); + if (retinternal == null) return (null); + Member [] retval = new Member[retinternal.length]; + for (int i = 0; i < retinternal.length; i++) { + String [] entry = (String []) retinternal[i]; + retval[i] = new Member(entry[0], entry[1]); + } + return (retval); + } +} + +class MoiraConnectInternal { + static native void connect(String server) throws MoiraException; + static native void auth() throws MoiraException; + static native void proxy(String user) throws MoiraException; + static native void disconnect(); + static native Object [] mr_query(String command, String [] args) throws MoiraException; + static { + System.loadLibrary("moirajava"); + } +} diff --git a/webmoira/mit/moira/MoiraException.java b/webmoira/mit/moira/MoiraException.java new file mode 100644 index 00000000..87ac82f7 --- /dev/null +++ b/webmoira/mit/moira/MoiraException.java @@ -0,0 +1,19 @@ +package mit.moira; + +public class MoiraException extends Exception { + int code = 0; + + MoiraException(String mess) { + super(mess); + } + + MoiraException(String mess, int code) { + super(mess); + this.code = code; + } + + public int getCode() { + return (code); + } + +} diff --git a/webmoira/mit/moira/MoiraServlet.java b/webmoira/mit/moira/MoiraServlet.java new file mode 100644 index 00000000..eeebbe3a --- /dev/null +++ b/webmoira/mit/moira/MoiraServlet.java @@ -0,0 +1,1339 @@ +package mit.moira; + +import javax.servlet.*; +import javax.servlet.http.*; +import java.io.*; +import java.util.*; +import java.sql.SQLException; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.ResourceBundle; + +public class MoiraServlet extends HttpServlet { + static final String MOIRA_SERVER = "moira.mit.edu"; + Object LOCK = new Object(); // Used to synchronize Moira authentication + // with the ticket fetching thread + Kticket kt = new Kticket("jis5", "foobar", "ATHENA.MIT.EDU", LOCK); + boolean ktinit = false; + private static final int MAXDISPLAY = 10; + Hashtable FileParts = new Hashtable(); + Hashtable FileTimes = new Hashtable(); + static final SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss"); + static final int KE_RD_AP_BADD = 39525414; // This is a kludge + + + public synchronized void service(HttpServletRequest request, HttpServletResponse response) { + System.err.println("MoiraSerlvet: Service"); + System.err.println("---------------------"); + + if (!ktinit) { // Once only initialization + ktinit = true; + Thread t = new Thread(kt); + t.start(); + } + + String msg = null; + + Hashtable qv = new Hashtable(); + + String modifier = ""; + + try { // Get Parameters + Enumeration e = request.getParameterNames(); + while (e.hasMoreElements()) { + String pname = (String)e.nextElement(); + String [] pvaluearray = request.getParameterValues(pname); + String pvalue = ""; + if (pvaluearray.length > 1) { + for (int i = 0; i < pvaluearray.length; i++) + pvalue += pvaluearray[i]; + } else pvalue = pvaluearray[0]; + + System.err.println(pname + " = " + pvalue); + qv.put(pname, pvalue); + } + + modifier = (String)qv.get("modifier"); + if (modifier == null) modifier = ""; + + if (modifier.equals("showform")) { + do_showform(qv, request, response); + return; + } else if (modifier.equals("showurl")) { + do_showurl(qv, request, response); + return; + } else if (modifier.equals("makesession")) { + request.getSession(true); + return; + } + + String operation = (String) qv.get("operation"); + if (operation == null) { + try { + DataOutputStream out = new DataOutputStream(response.getOutputStream()); + response.setContentType("text/html"); + if (modifier.equals("getfile")) + out.writeBytes("You need to select an operation from the list on the left!"); + else if (modifier.equals("displayonly")) + out.writeBytes("error"); + else if (modifier.equals("getlistname")) { + out.writeBytes(do_getlistinput(qv, request, response)); + } + out.flush(); + } catch (Exception ee) { + ee.printStackTrace(); + } + return; + } + if (operation.charAt(operation.length()-2) == '\r') // Trim newline stuff + operation = operation.substring(0, operation.length() - 2); + if (modifier.equals("getlistname")) { + msg = do_getlistinput(qv, request, response); + if (msg.equals("")) return; + } else if (modifier.equals("getfile")) { + msg = getfile(operation); + } else if (operation.equals("getmembers")) { + if (modifier.equals("displayonly")) { + msg = "show members"; + } else + msg = do_getmembers(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("addfinal")) { + if (modifier.equals("displayonly")) { + msg = "added members"; + } else + msg = do_addfinal(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("editme")) { + if (modifier.equals("displayonly")) { + msg = "add or remove yourself from a list"; + } else + msg = do_editme(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("removemembers")) { + if (modifier.equals("displayonly")) { + msg = "edit/remove members"; + } else + msg = do_removemembers(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("delmembers")) { + if (modifier.equals("displayonly")) { + msg = "edit/remove members"; + } else + msg = do_delmember(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("delconfirm")) { + if (modifier.equals("displayonly")) { + msg = "member deleted"; + } else + msg = do_delconfirm(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("addmember")) { + if (modifier.equals("displayonly")) { + msg = "add member(s)"; + } else + msg = do_addmember(qv, request, response); + if (msg.equals("")) return; + } else if (operation.equals("addme")) { + if (modifier.equals("displayonly")) { + msg = "added to list"; + } else + msg = do_addremme(qv, request, response, true); + } else if (operation.equals("delme")) { + if (modifier.equals("displayonly")) { + msg = "removed from list"; + } else + msg = do_addremme(qv, request, response, false); + } else if (operation.equals("displaylistinfo")) { + if (modifier.equals("displayonly")) { + msg = "display list characteristics"; + } else + msg = do_showlistinfo(qv, request, response); + } else if (operation.equals("updatelistinfo")) { + if (modifier.equals("displayonly")) { + msg = "update list characteristics"; + } else + msg = do_updatelistinfo(qv, request, response); + } else if (operation.equals("updatelistinfoconf")) { + if (modifier.equals("displayonly")) { + msg = "update list characteristics"; + } else + msg = do_updatelistinfoconf(qv, request, response); + } else { + sendError("Unimplemented Operation: " + (String)qv.get("operation"), response, true); + return; + } + + } catch (AuthenticationError e) { + msg = "" + e.getMessage() + "
\r\n"; + } catch (Exception e) { + msg += "