]> asedeno.scripts.mit.edu Git - 1ts-debian.git/blobdiff - zephyr/server/uloc.c
Initial revision
[1ts-debian.git] / zephyr / server / uloc.c
diff --git a/zephyr/server/uloc.c b/zephyr/server/uloc.c
new file mode 100644 (file)
index 0000000..a319b22
--- /dev/null
@@ -0,0 +1,1101 @@
+/* This file is part of the Project Athena Zephyr Notification System.
+ * It contains functions for the User Locator service.
+ *
+ *     Created by:     John T. Kohl
+ *
+ *     $Id: uloc.c,v 1.59 2000/01/22 18:36:02 ghudson Exp $
+ *
+ *     Copyright (c) 1987,1988 by the Massachusetts Institute of Technology.
+ *     For copying and distribution information, see the file
+ *     "mit-copyright.h". 
+ */
+
+#include <zephyr/mit-copyright.h>
+#include "zserver.h"
+#include <sys/socket.h>
+
+#ifndef lint
+#ifndef SABER
+static const char rcsid_uloc_c[] =
+"$Id: uloc.c,v 1.59 2000/01/22 18:36:02 ghudson Exp $";
+#endif /* SABER */
+#endif /* lint */
+
+/*
+ * The user locator functions.
+ *
+ * External functions:
+ *
+ * void ulocate_dispatch(notice, auth, who, server)
+ *     ZNotice_t *notice;
+ *     int auth;
+ *     struct sockaddr_in *who;
+ *     Server *server;
+ *
+ * void ulogin_dispatch(notice, auth, who, server)
+ *     ZNotice_t *notice;
+ *     int auth;
+ *     struct sockaddr_in *who;
+ *     Server *server;
+ *
+ * void uloc_hflush(addr)
+ *     struct in_addr *addr;
+ *
+ * void uloc_flush_client(sin)
+ *     struct sockaddr_in *sin;
+ *
+ * Code_t uloc_send_locations()
+ *
+ * void uloc_dump_locs(fp)
+ *     FILE *fp;
+ */
+
+/*
+ * The user locator.
+ * We maintain an array of Location sorted by user (so we can do
+ * binary searches), growing and shrinking it as necessary.
+ */
+
+/* WARNING: make sure this is the same as the number of strings you */
+/* plan to hand back to the user in response to a locate request, */
+/* else you will lose.  See ulogin_locate() and uloc_send_locations() */  
+#define        NUM_FIELDS      3
+
+typedef enum _Exposure_type {
+    NONE,
+    OPSTAFF_VIS,
+    REALM_VIS,
+    REALM_ANN,
+    NET_VIS,
+    NET_ANN
+} Exposure_type;
+
+typedef struct _Location {
+    String *user;
+    String *machine;
+    char *time;                        /* in ctime format */
+    String *tty;
+    struct sockaddr_in addr;   /* IP address and port of location */
+    Exposure_type exposure;
+} Location;
+
+#define NOLOC          1
+#define QUIET          -1
+#define UNAUTH         -2
+
+static void ulogin_locate __P((ZNotice_t *notice, struct sockaddr_in *who,
+                              int auth)),
+ulogin_flush_user __P((ZNotice_t *notice));
+static Location *ulogin_find __P((char *user, struct in_addr *host,
+                                 unsigned int port));
+static Location *ulogin_find_user __P((char *user));
+static int ulogin_setup __P((ZNotice_t *notice, Location *locs,
+                            Exposure_type exposure, struct sockaddr_in *who)),
+ulogin_add_user __P((ZNotice_t *notice, Exposure_type exposure,
+                    struct sockaddr_in *who)),
+ulogin_parse __P((ZNotice_t *notice, Location *locs));
+static Exposure_type ulogin_remove_user __P((ZNotice_t *notice,
+                                            struct sockaddr_in *who,
+                                            int *err_return));
+static void login_sendit __P((ZNotice_t *notice, int auth,
+                             struct sockaddr_in *who, int external));
+static char **ulogin_marshal_locs __P((ZNotice_t *notice, int *found,
+                                      int auth));
+
+static int ul_equiv __P((Location *l1, Location *l2));
+
+static void free_loc __P((Location *loc));
+static void ulogin_locate_forward __P((ZNotice_t *notice,
+                                      struct sockaddr_in *who, Realm *realm));
+
+static Location *locations = NULL; /* ptr to first in array */
+static int num_locs = 0;       /* number in array */
+
+/*
+ * Dispatch a LOGIN notice.
+ */
+
+Code_t
+ulogin_dispatch(notice, auth, who, server)
+    ZNotice_t *notice;
+    int auth;
+    struct sockaddr_in *who;
+    Server *server;
+{
+    Exposure_type retval;
+    int err_ret;
+
+    if (strcmp(notice->z_opcode, LOGIN_USER_LOGOUT) == 0) {
+       retval = ulogin_remove_user(notice, who, &err_ret);
+       switch (retval) {
+         case NONE:
+           if (err_ret == UNAUTH) {
+               if (server == me_server)
+                   clt_ack(notice, who, AUTH_FAILED);
+               return ZERR_NONE;
+           } else if (err_ret == NOLOC) {
+               if (server == me_server)
+                   clt_ack(notice, who, NOT_FOUND);
+               return ZERR_NONE;
+           } 
+           syslog(LOG_ERR,"bogus location exposure NONE, %s",
+                  notice->z_sender);
+           break;
+         case OPSTAFF_VIS:
+         case REALM_VIS:
+           /* he is not announced to people.  Silently ack */
+           if (server == me_server)
+               ack(notice, who);
+           break;
+         case REALM_ANN:
+         case NET_VIS:
+           if (server == me_server)
+               sendit(notice, 1, who, 0);
+           break;
+         case NET_ANN:
+           /* currently no distinction between these.
+              just announce */
+           /* we assume that if this user is at a certain
+              IP address, we can trust the logout to be
+              authentic.  ulogin_remove_user checks the
+              ip addrs */
+           if (server == me_server)
+               sendit(notice, 1, who, 1);
+           break;
+         default:
+           syslog(LOG_ERR,"bogus location exposure %d/%s",
+                  (int) retval, notice->z_sender);
+           break;
+       }
+       if (server == me_server) /* tell the other servers */
+           server_forward(notice, auth, who);
+       return ZERR_NONE;
+    }
+    if (!bdumping && 
+       (!auth || strcmp(notice->z_sender, notice->z_class_inst) != 0))  {
+       zdbug((LOG_DEBUG,"unauthentic ulogin: %d %s %s", auth,
+              notice->z_sender, notice->z_class_inst));
+       if (server == me_server)
+           clt_ack(notice, who, AUTH_FAILED);
+       return ZERR_NONE;
+    }
+    if (strcmp(notice->z_opcode, LOGIN_USER_FLUSH) == 0) {
+       ulogin_flush_user(notice);
+       if (server == me_server)
+           ack(notice, who);
+    } else if (strcmp(notice->z_opcode, EXPOSE_NONE) == 0) {
+       ulogin_remove_user(notice, who, &err_ret);
+       if (err_ret == UNAUTH) {
+           if (server == me_server)
+               clt_ack(notice, who, AUTH_FAILED);
+           return ZERR_NONE;
+       } else if (err_ret == NOLOC) {
+           if (server == me_server)
+               clt_ack(notice, who, NOT_FOUND);
+           return ZERR_NONE;
+       }
+       if (server == me_server) {
+           ack(notice, who);
+           server_forward(notice, auth, who);
+       }
+       return ZERR_NONE;
+    } else if (strcmp(notice->z_opcode, EXPOSE_OPSTAFF) == 0) {
+       err_ret = ulogin_add_user(notice, OPSTAFF_VIS, who);
+       if (server == me_server) {
+           if (err_ret)
+               nack(notice, who);
+           else
+               ack(notice, who);
+       }
+    } else if (strcmp(notice->z_opcode, EXPOSE_REALMVIS) == 0) {
+       err_ret = ulogin_add_user(notice, REALM_VIS, who);
+       if (server == me_server) { /* realm vis is not broadcast,
+                                     so we ack it here */
+           if (err_ret)
+               nack(notice, who);
+           else
+               ack(notice, who);
+       }
+    } else if (!strcmp(notice->z_opcode, EXPOSE_REALMANN)) {
+       err_ret = ulogin_add_user(notice, REALM_ANN, who);
+       if (server == me_server) { /* announce to the realm */
+           if (err_ret)
+               nack(notice, who);
+           else
+               login_sendit(notice, auth, who, 0);
+       }
+    } else if (!strcmp(notice->z_opcode, EXPOSE_NETVIS)) {
+       err_ret = ulogin_add_user(notice, NET_VIS, who);
+       if (server == me_server) { /* announce to the realm */
+           if (err_ret)
+               nack(notice, who);
+           else
+               login_sendit(notice, auth, who, 0);
+       }
+    } else if (!strcmp(notice->z_opcode, EXPOSE_NETANN)) {
+       err_ret = ulogin_add_user(notice, NET_ANN, who);
+       if (server == me_server) { /* tell the world */
+           if (err_ret)
+               nack(notice, who);
+           else
+               login_sendit(notice, auth, who, 1);
+       }
+    } else {
+       syslog(LOG_ERR, "unknown ulog opcode %s", notice->z_opcode);
+       if (server == me_server)
+           nack(notice, who);
+       return ZERR_NONE;
+    }
+    if (server == me_server)
+       server_forward(notice, auth, who);
+    return ZERR_NONE;
+}
+
+static void
+login_sendit(notice, auth, who, external)
+    ZNotice_t *notice;
+    int auth;
+    struct sockaddr_in *who;
+    int external;
+{
+    ZNotice_t log_notice;
+
+    /* we must copy the notice struct here because we need the original
+       for forwarding.  We needn't copy the private data of the notice,
+       since that isn't modified by sendit and its subroutines. */
+
+    log_notice = *notice;
+
+    log_notice.z_opcode = LOGIN_USER_LOGIN;
+    sendit(&log_notice, auth, who, external);
+}
+
+
+/*
+ * Dispatch a LOCATE notice.
+ */
+Code_t
+ulocate_dispatch(notice, auth, who, server)
+    ZNotice_t *notice;
+    int auth;
+    struct sockaddr_in *who;
+    Server *server;
+{
+    char *cp;
+    Realm *realm;
+
+    if (!strcmp(notice->z_opcode, LOCATE_LOCATE)) {
+       /* we are talking to a current-rev client; send an ack */
+       ack(notice, who);
+       cp = strchr(notice->z_class_inst, '@');
+       if (cp && (realm = realm_get_realm_by_name(cp + 1)))
+           ulogin_locate_forward(notice, who, realm);
+       else
+           ulogin_locate(notice, who, auth);
+       return ZERR_NONE;
+    } else {
+       syslog(LOG_ERR, "unknown uloc opcode %s", notice->z_opcode);
+       if (server == me_server)
+           nack(notice, who);
+       return ZERR_NONE;
+    }
+}
+
+/*
+ * Flush all locations at the address.
+ */
+
+void
+uloc_hflush(addr)
+    struct in_addr *addr;
+{
+    Location *loc;
+    int i = 0, new_num = 0;
+
+    if (num_locs == 0)
+       return;                 /* none to flush */
+
+    /* slightly inefficient, assume the worst, and allocate enough space */
+    loc = (Location *) malloc(num_locs *sizeof(Location));
+    if (!loc) {
+       syslog(LOG_CRIT, "uloc_flush alloc");
+       abort();
+    }
+
+    /* copy entries which don't match */
+    while (i < num_locs) {
+       if (locations[i].addr.sin_addr.s_addr != addr->s_addr)
+           loc[new_num++] = locations[i];
+       else
+           free_loc(&locations[i]);
+       i++;
+    }
+
+    free(locations);
+    locations = NULL;
+
+    if (!new_num) {
+       free(loc);
+       loc = NULL;
+       num_locs = new_num;
+
+       return;
+    }
+    locations = loc;
+    num_locs = new_num;
+
+    /* all done */
+    return;
+}
+
+void
+uloc_flush_client(sin)
+    struct sockaddr_in *sin;
+{
+    Location *loc;
+    int i = 0, new_num = 0;
+
+    if (num_locs == 0)
+       return;                 /* none to flush */
+
+    /* slightly inefficient, assume the worst, and allocate enough space */
+    loc = (Location *) malloc(num_locs *sizeof(Location));
+    if (!loc) {
+       syslog(LOG_CRIT, "uloc_flush_clt alloc");
+       abort();
+    }
+
+    /* copy entries which don't match */
+    while (i < num_locs) {
+       if ((locations[i].addr.sin_addr.s_addr != sin->sin_addr.s_addr)
+           || (locations[i].addr.sin_port != sin->sin_port)) {
+           loc[new_num++] = locations[i];
+       } else {
+           free_loc(&locations[i]);
+       }
+       i++;
+    }
+
+    free(locations);
+    locations = NULL;
+
+    if (!new_num) {
+       free(loc);
+       loc = NULL;
+       num_locs = new_num;
+
+       return;
+    }
+    locations = loc;
+    num_locs = new_num;
+
+#ifdef DEBUG
+    if (zdebug) {
+       int i;
+
+       for (i = 0; i < num_locs; i++) {
+           syslog(LOG_DEBUG, "%s/%d", locations[i].user->string,
+                  (int) locations[i].exposure);
+       }
+    }
+#endif
+    /* all done */
+    return;
+}
+
+/*
+ * Send the locations for host for a brain dump
+ */
+
+/*ARGSUSED*/
+Code_t
+uloc_send_locations()
+{
+    Location *loc;
+    int i;
+    char *lyst[NUM_FIELDS];
+    char *exposure_level;
+    Code_t retval;
+
+    for (i = 0, loc = locations; i < num_locs; i++, loc++) {
+       lyst[0] = (char *) loc->machine->string;
+       lyst[1] = (char *) loc->time;
+       lyst[2] = (char *) loc->tty->string;
+
+       switch (loc->exposure) {
+         case OPSTAFF_VIS:
+           exposure_level = EXPOSE_OPSTAFF;
+           break;
+         case REALM_VIS:
+           exposure_level = EXPOSE_REALMVIS;
+           break;
+         case REALM_ANN:
+           exposure_level = EXPOSE_REALMANN;
+           break;
+         case NET_VIS:
+           exposure_level = EXPOSE_NETVIS;
+           break;
+         case NET_ANN:
+           exposure_level = EXPOSE_NETANN;
+           break;
+         default:
+           syslog(LOG_ERR,"broken location state %s/%d",
+                  loc->user->string, (int) loc->exposure);
+           break;
+       }
+       retval = bdump_send_list_tcp(ACKED, &loc->addr, LOGIN_CLASS,
+                                    loc->user->string, exposure_level, myname,
+                                    "", lyst, NUM_FIELDS);
+       if (retval != ZERR_NONE) {
+           syslog(LOG_ERR, "uloc_send_locs: %s", error_message(retval));
+           return(retval);
+       }
+    }
+    return ZERR_NONE;
+}
+
+/*
+ * Add the user to the internal table of locations.
+ */
+
+static int
+ulogin_add_user(notice, exposure, who)
+    ZNotice_t *notice;
+    Exposure_type exposure;
+    struct sockaddr_in *who;
+{
+    Location *loc, *oldlocs, newloc;
+    int i;
+
+    loc = ulogin_find(notice->z_class_inst, &who->sin_addr, notice->z_port);
+    if (loc) {
+       /* Update the time, tty, and exposure on the existing location. */
+       loc->exposure = exposure;
+       if (ulogin_parse(notice, &newloc) == 0) {
+           free_string(loc->tty);
+           loc->tty = dup_string(newloc.tty);
+           free(loc->time);
+           loc->time = strsave(newloc.time);
+           free_loc(&newloc);
+       }
+       return 0;
+    }
+
+    oldlocs = locations;
+
+    locations = (Location *) malloc((num_locs + 1) * sizeof(Location));
+    if (!locations) {
+       syslog(LOG_ERR, "zloc mem alloc");
+       locations = oldlocs;
+       return 1;
+    }
+
+    if (num_locs == 0) {       /* first one */
+       if (ulogin_setup(notice, locations, exposure, who)) {
+           free(locations);
+           locations = NULL;
+           return 1;
+       }
+       num_locs = 1;
+       goto dprnt;
+    }
+
+    /* not the first one, insert him */
+
+    if (ulogin_setup(notice, &newloc, exposure, who)) {
+       free(locations);
+       locations = oldlocs;
+       return 1;
+    }
+    num_locs++;
+
+    /* copy old locs */
+    i = 0;
+    while ((i < num_locs-1) &&
+          (comp_string(oldlocs[i].user,newloc.user) < 0)) {
+       locations[i] = oldlocs[i];
+       i++;
+    }
+
+    /* add him in here */
+    locations[i++] = newloc;
+
+    /* copy the rest */
+    while (i < num_locs) {
+       locations[i] = oldlocs[i - 1];
+       i++;
+    }
+    if (oldlocs)
+       free(oldlocs);
+
+  dprnt:
+    return 0;
+}
+
+/*
+ * Set up the location locs with the information in the notice.
+ */ 
+
+static int
+ulogin_setup(notice, locs, exposure, who)
+    ZNotice_t *notice;
+    Location *locs;
+    Exposure_type exposure;
+    struct sockaddr_in *who;
+{
+    if (ulogin_parse(notice, locs))
+       return 1;
+
+    locs->exposure = exposure;
+    locs->addr.sin_family = AF_INET;
+    locs->addr.sin_addr.s_addr = who->sin_addr.s_addr;
+    locs->addr.sin_port = notice->z_port;
+    return(0);
+}
+
+/*
+ * Parse the location information in the notice, and fill it into *locs
+ */
+
+static int
+ulogin_parse(notice, locs)
+    ZNotice_t *notice;
+    Location *locs;
+{
+    char *cp, *base;
+    int nulls = 0;
+
+    if (!notice->z_message_len) {
+       syslog(LOG_ERR, "short ulogin");
+       return 1;
+    }
+
+    base = notice->z_message;
+    for (cp = base; cp < base + notice->z_message_len; cp++) {
+       if (!*cp)
+           nulls++;
+    }
+    if (nulls < 3) {
+       syslog(LOG_ERR, "zloc bad format from user %s (only %d fields)",
+              notice->z_sender, nulls);
+       return 1;
+    }
+
+    locs->user = make_string(notice->z_class_inst,0);
+
+    cp = base;
+    locs->machine = make_string(cp,0);
+
+    cp += (strlen(cp) + 1);
+    locs->time = strsave(cp);
+
+    /* This field might not be null-terminated */
+    cp += (strlen(cp) + 1);
+    locs->tty = make_string(cp, 0);
+
+    return 0;
+}      
+
+
+static Location *
+ulogin_find(user, host, port)
+    char *user;
+    struct in_addr *host;
+    unsigned int port;
+{
+    Location *loc;
+    String *str;
+
+    /* Find the first location for this user. */
+    loc = ulogin_find_user(user);
+    if (!loc)
+       return NULL;
+
+    /* Look for a location which matches the host and port. */
+    str = make_string(user, 0);
+    while (loc < locations + num_locs && loc->user == str) {
+       if (loc->addr.sin_addr.s_addr == host->s_addr
+           && loc->addr.sin_port == port) {
+           free_string(str);
+           return loc;
+       }
+       loc++;
+    }
+
+    free_string(str);
+    return NULL;
+}
+
+/*
+ * Return a pointer to the first instance of this user@realm in the
+ * table.
+ */
+
+static Location *
+ulogin_find_user(user)
+    char *user;
+{
+    int i, rlo, rhi;
+    int compar;
+    String *str;
+
+    if (!locations)
+       return(NULL);
+
+    str = make_string(user, 0);
+
+    /* i is the current midpoint location, rlo is the lowest we will
+     * still check, and rhi is the highest we will still check. */
+
+    i = num_locs / 2;
+    rlo = 0;
+    rhi = num_locs - 1;
+
+    while ((compar = comp_string(locations[i].user, str)) != 0) {
+       if (compar < 0)
+           rlo = i + 1;
+       else
+           rhi = i - 1;
+       if (rhi - rlo < 0) {
+           free_string(str);
+           return NULL;
+       }
+       i = (rhi + rlo) / 2;
+    }
+
+    /* Back up to the first location for this user. */
+    while (i > 0 && locations[i - 1].user == str)
+       i--;
+    free_string(str);
+    return &locations[i];
+}
+
+static int
+ul_equiv(l1, l2)
+    Location *l1, *l2;
+{
+    if (l1->machine != l2->machine)
+       return 0;
+    if (l1->tty != l2->tty)
+       return 0;
+    return 1;
+}
+
+/*
+ * remove the user specified in notice from the internal table
+ */
+
+static Exposure_type
+ulogin_remove_user(notice, who, err_return)
+    ZNotice_t *notice;
+    struct sockaddr_in *who;
+    int *err_return;
+{
+    Location *new_locs, *loc;
+    int i = 0;
+    Exposure_type quiet;
+
+    *err_return = 0;
+    loc = ulogin_find(notice->z_class_inst, &who->sin_addr, notice->z_port);
+    if (!loc) {
+       *err_return = NOLOC;
+       return NONE;
+    }
+
+    quiet = loc->exposure;
+
+    if (--num_locs == 0) {     /* last one */
+       free_loc(locations);
+       free(locations);
+       locations = NULL;
+       return quiet;
+    }
+
+    new_locs = (Location *) malloc(num_locs * sizeof(Location));
+    if (!new_locs) {
+       syslog(LOG_CRIT, "ul_rem alloc");
+       abort();
+    }
+
+    /* copy old entries */
+    while (i < num_locs && &locations[i] < loc) {
+       new_locs[i] = locations[i];
+       i++;
+    }
+
+    /* free up this one */
+    free_loc(&locations[i]);
+    i++;                       /* skip over this one */
+
+    /* copy the rest */
+    while (i <= num_locs) {
+       new_locs[i - 1] = locations[i];
+       i++;
+    }
+
+    free(locations);
+
+    locations = new_locs;
+
+    /* all done */
+    return quiet;
+}
+
+/*
+ * remove all locs of the user specified in notice from the internal table
+ */
+
+static void
+ulogin_flush_user(notice)
+    ZNotice_t *notice;
+{
+    Location *loc, *loc2;
+    int i, j, num_match, num_left;
+
+    i = num_match = num_left = 0;
+
+    if (!(loc2 = ulogin_find_user(notice->z_class_inst)))
+       return;
+
+    /* compute # locations left in the list, after loc2 (inclusive) */
+    num_left = num_locs - (loc2 - locations);
+
+    while (num_left &&
+          !strcasecmp(loc2[num_match].user->string,
+                      notice->z_class_inst)) {
+       /* as long as we keep matching, march up the list */
+       num_match++;
+       num_left--;
+    }
+    if (num_locs == num_match) { /* no other locations left */
+       for (j = 0; j < num_match; j++)
+           free_loc(&locations[j]); /* free storage */
+       free (locations);
+       locations = NULL;
+       num_locs = 0;
+       return;
+    }
+
+    loc = (Location *) malloc((num_locs - num_match) * sizeof(Location));
+    if (!loc) {
+       syslog(LOG_CRIT, "ul_rem alloc");
+       abort();
+    }
+
+    /* copy old entries */
+    while (i < num_locs && &locations[i] < loc2) {
+       loc[i] = locations[i];
+       i++;
+    }
+       
+    for(j = 0; j < num_match; j++) {
+       free_loc(&locations[i]);
+       i++;
+    }
+
+    /* copy the rest */
+    while (i < num_locs) {
+       loc[i - num_match] = locations[i];
+       i++;
+    }
+
+    free(locations);
+
+    locations = loc;
+    num_locs -= num_match;
+
+#ifdef DEBUG
+    if (zdebug) {
+       int i;
+
+       for (i = 0; i < num_locs; i++) {
+           syslog(LOG_DEBUG, "%s/%d", locations[i].user->string,
+                  (int) locations[i].exposure);
+       }
+    }
+#endif
+}
+
+
+static void
+ulogin_locate(notice, who, auth)
+    ZNotice_t *notice;
+    struct sockaddr_in *who;
+    int auth;
+{
+    char **answer;
+    int found;
+    Code_t retval;
+    struct sockaddr_in send_to_who;
+
+    answer = ulogin_marshal_locs(notice, &found, auth);
+
+    send_to_who = *who;
+    send_to_who.sin_port = notice->z_port;
+
+    retval = ZSetDestAddr(&send_to_who);
+    if (retval != ZERR_NONE) {
+       syslog(LOG_WARNING, "ulogin_locate set addr: %s",
+              error_message(retval));
+       if (answer)
+           free(answer);
+       return;
+    }
+
+    notice->z_kind = ACKED;
+
+    /* use xmit_frag() to send each piece of the notice */
+
+    retval = ZSrvSendRawList(notice, answer, found * NUM_FIELDS, xmit_frag);
+    if (retval != ZERR_NONE)
+       syslog(LOG_WARNING, "ulog_locate xmit: %s", error_message(retval));
+    if (answer)
+       free(answer);
+}
+
+/*
+ * Locate the user and collect the locations into an array.  Return the # of
+ * locations in *found.
+ */
+
+static char **
+ulogin_marshal_locs(notice, found, auth)
+    ZNotice_t *notice;
+    int *found;
+    int auth;
+{
+    Location **matches = (Location **) 0;
+    Location *loc;
+    char **answer;
+    int i = 0;
+    String *inst;
+    int local = (auth && sender_in_realm(notice));
+
+    *found = 0;                        /* # of matches */
+
+    loc = ulogin_find_user(notice->z_class_inst);
+    if (!loc)
+       return(NULL);
+
+    i = loc - locations;
+
+    inst = make_string(notice->z_class_inst,0);
+    while (i < num_locs && (inst == locations[i].user)) {
+       /* these locations match */
+       switch (locations[i].exposure) {
+         case OPSTAFF_VIS:
+           i++;
+           continue;
+         case REALM_VIS:
+         case REALM_ANN:
+           if (!local) {
+               i++;
+               continue;
+           }
+         case NET_VIS:
+         case NET_ANN:
+         default:
+           break;
+       }
+       if (!*found) {
+           matches = (Location **) malloc(sizeof(Location *));
+           if (!matches) {
+               syslog(LOG_ERR, "ulog_loc: no mem");
+               break;  /* from the while */
+           }
+           matches[0] = &locations[i];
+           (*found)++;
+       } else {
+           matches = (Location **) realloc(matches,
+                                           ++(*found) * sizeof(Location *));
+           if (!matches) {
+               syslog(LOG_ERR, "ulog_loc: realloc no mem");
+               *found = 0;
+               break;  /* from the while */
+           }
+           matches[*found - 1] = &locations[i];
+       }
+       i++;
+    }
+    free_string(inst);
+
+    /* OK, now we have a list of user@host's to return to the client
+       in matches */
+       
+       
+#ifdef DEBUG
+    if (zdebug) {
+       for (i = 0; i < *found ; i++)
+           zdbug((LOG_DEBUG,"found %s",
+                  matches[i]->user->string));
+    }
+#endif
+       
+    /* coalesce the location information into a list of char *'s */
+    answer = (char **) malloc((*found) * NUM_FIELDS * sizeof(char *));
+    if (!answer) {
+       syslog(LOG_ERR, "zloc no mem(answer)");
+       *found = 0;
+    } else
+       for (i = 0; i < *found ; i++) {
+           answer[i * NUM_FIELDS] = matches[i]->machine->string;
+           answer[i * NUM_FIELDS + 1] = matches[i]->time;
+           answer[i * NUM_FIELDS + 2] = matches[i]->tty->string;
+       }
+       
+    if (matches)
+       free(matches);
+    return answer;
+}
+
+void
+uloc_dump_locs(fp)
+    FILE *fp;
+{
+    int i;
+
+    for (i = 0; i < num_locs; i++) {
+       fputs("'", fp);
+       dump_quote(locations[i].user->string, fp);
+       fputs("' '", fp);
+       dump_quote(locations[i].machine->string, fp);
+       fputs("' '", fp);
+       dump_quote(locations[i].time, fp);
+       fputs("' '", fp);
+       dump_quote(locations[i].tty->string, fp);
+       fputs("' ", fp);
+       switch (locations[i].exposure) {
+         case OPSTAFF_VIS:
+           fputs("OPSTAFF", fp);
+           break;
+         case REALM_VIS:
+           fputs("RLM_VIS", fp);
+           break;
+         case REALM_ANN:
+           fputs("RLM_ANN", fp);
+           break;
+         case NET_VIS:
+           fputs("NET_VIS", fp);
+           break;
+         case NET_ANN:
+           fputs("NET_ANN", fp);
+           break;
+         default:
+           fprintf(fp, "? %d ?", locations[i].exposure);
+           break;
+       }
+       fprintf(fp, " %s/%d\n", inet_ntoa(locations[i].addr.sin_addr),
+               ntohs(locations[i].addr.sin_port));
+    }
+}
+
+static void
+free_loc(loc)
+    Location *loc;
+{
+    free_string(loc->user);
+    free_string(loc->machine);
+    free_string(loc->tty);
+    free(loc->time);
+    return;
+}
+
+static void
+ulogin_locate_forward(notice, who, realm)
+    ZNotice_t *notice;
+    struct sockaddr_in *who;
+    Realm *realm;
+{
+    ZNotice_t lnotice;
+
+    lnotice = *notice;
+    lnotice.z_opcode = REALM_REQ_LOCATE;
+  
+    realm_handoff(&lnotice, 1, who, realm, 0);
+}
+
+void
+ulogin_realm_locate(notice, who, realm)
+    ZNotice_t *notice;
+    struct sockaddr_in *who;
+    Realm *realm;
+{
+  char **answer;
+  int found;
+  Code_t retval;
+  ZNotice_t lnotice;
+  char *pack;
+  int packlen;
+  
+#ifdef DEBUG
+  if (zdebug)
+    zdbug((LOG_DEBUG, "ulogin_realm_locate"));
+#endif
+  
+  answer = ulogin_marshal_locs(notice, &found, 0/*AUTH*/);
+  
+  lnotice = *notice;
+  lnotice.z_opcode = REALM_ANS_LOCATE;
+  
+  if ((retval = ZFormatRawNoticeList(&lnotice, answer, found * NUM_FIELDS, &pack, &packlen)) != ZERR_NONE) {
+    syslog(LOG_WARNING, "ulog_rlm_loc format: %s",
+           error_message(retval));
+    
+    if (answer)
+      free(answer);
+    return;
+  }
+  if (answer)
+    free(answer);
+  
+  if ((retval = ZParseNotice(pack, packlen, &lnotice)) != ZERR_NONE) {
+    syslog(LOG_WARNING, "subscr_rlm_sendit parse: %s",
+           error_message(retval));
+    free(pack);
+    return;
+  }
+  
+  realm_handoff(&lnotice, 1, who, realm, 0);
+  free(pack);
+  
+  return;
+}
+
+void
+ulogin_relay_locate(notice, who)
+    ZNotice_t *notice;
+    struct sockaddr_in *who;
+{
+  ZNotice_t lnotice;
+  Code_t retval;
+  struct sockaddr_in newwho;
+  char *pack;
+  int packlen;
+  
+  newwho.sin_addr.s_addr = notice->z_sender_addr.s_addr;
+  newwho.sin_port = notice->z_port;
+  newwho.sin_family = AF_INET;
+  
+  if ((retval = ZSetDestAddr(&newwho)) != ZERR_NONE) {
+    syslog(LOG_WARNING, "uloc_relay_loc set addr: %s",
+           error_message(retval));
+    return;
+  }
+  
+  lnotice = *notice;
+  lnotice.z_opcode = LOCATE_LOCATE;
+  lnotice.z_kind = ACKED;
+  
+  if ((retval = ZFormatRawNotice(&lnotice, &pack, &packlen)) != ZERR_NONE) {
+    syslog(LOG_WARNING, "ulog_relay_loc format: %s",
+           error_message(retval));
+    return;
+  }
+  
+  if ((retval = ZSendPacket(pack, packlen, 0)) != ZERR_NONE) {
+    syslog(LOG_WARNING, "ulog_relay_loc xmit: %s",
+           error_message(retval));
+  }
+  free(pack);
+}
+