1240 lines
35 KiB
C
1240 lines
35 KiB
C
/* This file is part of the Project Athena Zephyr Notification System.
|
|
* It contains functions for dispatching a notice.
|
|
*
|
|
* Created by: John T. Kohl
|
|
*
|
|
* $Source$
|
|
* $Author$
|
|
*
|
|
* Copyright (c) 1987, 1991 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_dispatch_c[] =
|
|
"$Id$";
|
|
#endif
|
|
#endif
|
|
|
|
enum {
|
|
NACKTAB_HASHSIZE = 1023
|
|
};
|
|
inline static unsigned int
|
|
nacktab_hashval(struct sockaddr_in sa, ZUnique_Id_t uid) {
|
|
return (sa.sin_addr.s_addr ^
|
|
sa.sin_port ^
|
|
uid.zuid_addr.s_addr ^
|
|
uid.tv.tv_sec ^
|
|
uid.tv.tv_usec) % NACKTAB_HASHSIZE;
|
|
}
|
|
|
|
#define HOSTS_SIZE_INIT 256
|
|
|
|
/*
|
|
*
|
|
* External Routines:
|
|
*
|
|
* void dispatch(notice, auth, who)
|
|
* ZNotice_t *notice;
|
|
* int auth;
|
|
* struct sockaddr_in *who;
|
|
*
|
|
* void clt_ack(notice, who, sent)
|
|
* ZNotice_t *notice;
|
|
* struct sockaddr_in *who;
|
|
* Sent_type sent;
|
|
*
|
|
* void nack_release(client)
|
|
* Client *client;
|
|
*
|
|
* void sendit(notice, auth, who, external)
|
|
* ZNotice_t *notice;
|
|
* int auth;
|
|
* struct sockaddr_in *who;
|
|
* int external;
|
|
*
|
|
* void xmit(notice, dest, auth, client)
|
|
* ZNotice_t *notice;
|
|
* struct sockaddr_in *dest;
|
|
* int auth;
|
|
* Client *client;
|
|
*/
|
|
|
|
|
|
String *class_control, *class_admin, *class_hm, *class_ulogin, *class_ulocate;
|
|
|
|
int rexmit_times[] = REXMIT_TIMES;
|
|
|
|
static void nack_cancel(ZNotice_t *, struct sockaddr_in *);
|
|
static void dispatch(ZNotice_t *, int, struct sockaddr_in *, int);
|
|
static int send_to_dest(ZNotice_t *, int, Destination *dest, int, int);
|
|
static void hostm_deathgram(struct sockaddr_in *, Server *);
|
|
static char *hm_recipient(void);
|
|
|
|
Statistic realm_notices = {0, "inter-realm notices"};
|
|
Statistic interserver_notices = {0, "inter-server notices"};
|
|
Statistic hm_packets = {0, "hostmanager packets"};
|
|
Statistic control_notices = {0, "client control notices"};
|
|
Statistic message_notices = {0, "message notices"};
|
|
Statistic login_notices = {0, "login notices"};
|
|
Statistic i_s_ctls = {0, "inter-server control notices"};
|
|
Statistic i_s_logins = {0, "inter-server login notices"};
|
|
Statistic i_s_admins = {0, "inter-server admin notices"};
|
|
Statistic i_s_locates = {0, "inter-server locate notices"};
|
|
Statistic locate_notices = {0, "locate notices"};
|
|
Statistic admin_notices = {0, "admin notices"};
|
|
|
|
static Unacked *nacktab[NACKTAB_HASHSIZE];
|
|
static struct in_addr *hosts;
|
|
static int hosts_size = 0, num_hosts = 0;
|
|
|
|
#ifdef DEBUG
|
|
static void
|
|
dump_stats (void *arg)
|
|
{
|
|
syslog(LOG_INFO, "stats: %s: %d", hm_packets.str, hm_packets.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", control_notices.str,
|
|
control_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", message_notices.str,
|
|
message_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", login_notices.str, login_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", locate_notices.str, locate_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", admin_notices.str, admin_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", realm_notices.str, realm_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", interserver_notices.str,
|
|
interserver_notices.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", i_s_ctls.str, i_s_ctls.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", i_s_logins.str, i_s_logins.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", i_s_admins.str, i_s_admins.val);
|
|
syslog(LOG_INFO, "stats: %s: %d", i_s_locates.str, i_s_locates.val);
|
|
|
|
/* log stuff once an hour */
|
|
timer_set_rel ((long) 6*60*60, dump_stats, arg);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Handle an input packet.
|
|
* Warning: this function may be called from within a brain dump.
|
|
*/
|
|
|
|
void
|
|
handle_packet(void)
|
|
{
|
|
Code_t status;
|
|
ZPacket_t input_packet; /* from the network */
|
|
ZNotice_t new_notice; /* parsed from input_packet */
|
|
int input_len; /* len of packet */
|
|
struct sockaddr_in input_sin; /* Zconstructed for authent */
|
|
struct sockaddr_in whoisit; /* for holding peer's address */
|
|
int authentic; /* authentic flag */
|
|
Pending *pending; /* pending packet */
|
|
int from_server; /* packet is from another server */
|
|
ZRealm *realm; /* foreign realm ptr */
|
|
struct sockaddr_in *whence; /* pointer to actual origin */
|
|
#ifdef DEBUG
|
|
static int first_time = 1;
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
/* Dump statistics five minutes after startup */
|
|
if (first_time) {
|
|
first_time = 0;
|
|
timer_set_rel(5*60, dump_stats, NULL);
|
|
}
|
|
#endif
|
|
/* handle traffic */
|
|
|
|
if (otherservers[me_server_idx].queue) {
|
|
/* something here for me; take care of it */
|
|
zdbug((LOG_DEBUG, "internal queue process"));
|
|
|
|
pending = server_dequeue(me_server);
|
|
|
|
status = ZParseNotice(pending->packet, pending->len, &new_notice);
|
|
if (status != ZERR_NONE) {
|
|
syslog(LOG_ERR, "bad notice parse (%s): %s",
|
|
inet_ntoa(pending->who.sin_addr), error_message(status));
|
|
} else {
|
|
dispatch(&new_notice, pending->auth, &pending->who, 1);
|
|
}
|
|
server_pending_free(pending);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* nothing in internal queue, go to the external library
|
|
* queue/socket
|
|
*/
|
|
status = ZReceivePacket(input_packet, &input_len, &whoisit);
|
|
if (status != ZERR_NONE) {
|
|
syslog(LOG_ERR, "bad packet receive: %s from %s",
|
|
error_message(status), inet_ntoa(whoisit.sin_addr));
|
|
return;
|
|
}
|
|
npackets++;
|
|
status = ZParseNotice(input_packet, input_len, &new_notice);
|
|
if (status != ZERR_NONE) {
|
|
syslog(LOG_ERR, "bad notice parse (%s): %s",
|
|
inet_ntoa(whoisit.sin_addr), error_message(status));
|
|
return;
|
|
}
|
|
|
|
if (server_which_server(&whoisit)) {
|
|
/* we need to parse twice--once to get
|
|
the source addr, second to check
|
|
authentication */
|
|
notice_extract_address(&new_notice, &input_sin);
|
|
/* Should check to see if packet is from another realm's server,
|
|
or a client */
|
|
from_server = 1;
|
|
whence = &input_sin;
|
|
} else {
|
|
from_server = 0;
|
|
whence = &whoisit;
|
|
}
|
|
|
|
/* Don't bother checking authentication on client ACKs */
|
|
if (new_notice.z_kind == CLIENTACK) {
|
|
nack_cancel(&new_notice, &whoisit);
|
|
return;
|
|
}
|
|
|
|
/* Clients don't check auth of acks, nor do we make it so they
|
|
can in general, so this is safe. */
|
|
if (new_notice.z_kind == SERVACK || new_notice.z_kind == SERVNAK) {
|
|
authentic = ZAUTH_YES;
|
|
} else {
|
|
realm = realm_which_realm(whence);
|
|
authentic = ZCheckSrvAuthentication(&new_notice, whence, realm ? realm->name : NULL);
|
|
}
|
|
|
|
message_notices.val++;
|
|
dispatch(&new_notice, authentic, &whoisit, from_server);
|
|
return;
|
|
}
|
|
/*
|
|
* Dispatch a notice.
|
|
*/
|
|
|
|
static void
|
|
dispatch(ZNotice_t *notice,
|
|
int auth,
|
|
struct sockaddr_in *who,
|
|
int from_server)
|
|
{
|
|
Code_t status;
|
|
String *notice_class;
|
|
int authflag;
|
|
ZRealm *realm;
|
|
char *cp;
|
|
|
|
authflag = (auth == ZAUTH_YES);
|
|
|
|
if ((int) notice->z_kind < (int) UNSAFE ||
|
|
(int) notice->z_kind > (int) CLIENTACK) {
|
|
syslog(LOG_NOTICE, "bad notice kind 0x%x from %s", notice->z_kind,
|
|
inet_ntoa(who->sin_addr));
|
|
return;
|
|
}
|
|
|
|
if (notice->z_kind == CLIENTACK) {
|
|
nack_cancel(notice, who);
|
|
return;
|
|
}
|
|
|
|
notice_class = make_string(notice->z_class,1);
|
|
|
|
if (from_server) {
|
|
interserver_notices.val++;
|
|
status = server_dispatch(notice, authflag, who);
|
|
} else if (class_is_hm(notice_class)) {
|
|
hm_packets.val++;
|
|
status = hostm_dispatch(notice, authflag, who, me_server);
|
|
} else if (realm_which_realm(who) && !(class_is_admin(notice_class))) {
|
|
realm_notices.val++;
|
|
status = realm_dispatch(notice, authflag, who, me_server);
|
|
} else if (class_is_control(notice_class)) {
|
|
control_notices.val++;
|
|
status = control_dispatch(notice, authflag, who, me_server);
|
|
} else if (class_is_ulogin(notice_class)) {
|
|
login_notices.val++;
|
|
status = ulogin_dispatch(notice, authflag, who, me_server);
|
|
} else if (class_is_ulocate(notice_class)) {
|
|
locate_notices.val++;
|
|
status = ulocate_dispatch(notice, authflag, who, me_server);
|
|
} else if (class_is_admin(notice_class)) {
|
|
admin_notices.val++;
|
|
status = server_adispatch(notice, authflag, who, me_server);
|
|
} else {
|
|
if (!realm_bound_for_realm(ZGetRealm(), notice->z_recipient)) {
|
|
cp = strchr(notice->z_recipient, '@');
|
|
if (!cp ||
|
|
!(realm = realm_get_realm_by_name(cp + 1))) {
|
|
/* Foreign user, local realm */
|
|
sendit(notice, authflag, who, 0);
|
|
} else
|
|
realm_handoff(notice, authflag, who, realm, 1);
|
|
} else {
|
|
if (notice->z_recipient[0] == '@')
|
|
notice->z_recipient = "";
|
|
sendit(notice, authflag, who, 1);
|
|
}
|
|
free_string(notice_class);
|
|
return;
|
|
}
|
|
|
|
if (status == ZSRV_REQUEUE)
|
|
server_self_queue(notice, authflag, who);
|
|
free_string(notice_class);
|
|
}
|
|
|
|
/*
|
|
* Send a notice off to those clients who have subscribed to it.
|
|
*/
|
|
|
|
void
|
|
sendit(ZNotice_t *notice,
|
|
int auth,
|
|
struct sockaddr_in *who,
|
|
int external)
|
|
{
|
|
static int send_counter = 0;
|
|
char recipbuf[MAX_PRINCIPAL_SIZE], *recipp, *acl_sender;
|
|
int any = 0;
|
|
Acl *acl;
|
|
Destination dest;
|
|
String *class;
|
|
|
|
class = make_string(notice->z_class, 1);
|
|
if (realm_bound_for_realm(ZGetRealm(), notice->z_recipient)) {
|
|
ZRealm *rlm;
|
|
|
|
acl = class_get_acl(class);
|
|
if (acl != NULL) {
|
|
acl_sender = auth ? notice->z_sender : 0;
|
|
/* if from foreign realm server, disallow if not realm of sender */
|
|
rlm = realm_which_realm(who);
|
|
if (rlm) {
|
|
if (!realm_sender_in_realm(rlm->name, notice->z_sender)) {
|
|
syslog(LOG_WARNING, "sendit auth not verifiable %s (%s) from %s",
|
|
notice->z_class, rlm->name, notice->z_sender);
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
free_string(class);
|
|
return;
|
|
}
|
|
}
|
|
/* if not auth to transmit, fail */
|
|
if (!access_check(acl_sender, who, acl, TRANSMIT)) {
|
|
syslog(LOG_WARNING, "sendit unauthorized %s from %s%s",
|
|
notice->z_class, notice->z_sender, auth ? "" : " (unauth)");
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
free_string(class);
|
|
return;
|
|
}
|
|
/* sender != inst and not auth to send to others --> fail */
|
|
if (strcmp(notice->z_sender, notice->z_class_inst) != 0 &&
|
|
!access_check(acl_sender, who, acl, INSTUID)) {
|
|
syslog(LOG_WARNING, "sendit unauth uid %s%s %s.%s",
|
|
notice->z_sender, auth ? "" : " (unauth)",
|
|
notice->z_class, notice->z_class_inst);
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
free_string(class);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Increment the send counter, used to prevent duplicate sends to
|
|
* clients. On the off-chance that we wrap around to 0, skip over
|
|
* it to prevent missing clients which have never had a packet
|
|
* sent to them. */
|
|
send_counter++;
|
|
if (send_counter == 0)
|
|
send_counter = 1;
|
|
|
|
/* Send to clients subscribed to the triplet itself. */
|
|
dest.classname = class;
|
|
dest.inst = make_string(notice->z_class_inst, 1);
|
|
if (realm_bound_for_realm(ZGetRealm(), notice->z_recipient) &&
|
|
*notice->z_recipient == '@')
|
|
dest.recip = make_string("", 0);
|
|
else {
|
|
strncpy(recipbuf, notice->z_recipient, sizeof(recipbuf));
|
|
recipp = strrchr(recipbuf, '@');
|
|
if (recipp)
|
|
/* XXX if realm_expand_realm doesn't find a match
|
|
* it returns what's passed into it, causing an overlapping
|
|
* copy, the results of which are undefined.
|
|
*/
|
|
strncpy(recipp + 1, realm_expand_realm(recipp + 1),
|
|
sizeof(recipbuf) - (recipp - recipbuf) - 1);
|
|
dest.recip = make_string(recipbuf, 0);
|
|
}
|
|
|
|
if (send_to_dest(notice, auth, &dest, send_counter, external))
|
|
any = 1;
|
|
|
|
/* Send to clients subscribed to the triplet with the instance
|
|
* substituted with the wildcard instance. */
|
|
free_string(dest.inst);
|
|
dest.inst = wildcard_instance;
|
|
if (send_to_dest(notice, auth, &dest, send_counter, external))
|
|
any = 1;
|
|
|
|
free_string(class);
|
|
free_string(dest.recip);
|
|
if (any)
|
|
ack(notice, who);
|
|
else
|
|
nack(notice, who);
|
|
}
|
|
|
|
/*
|
|
* Send to each client in the list. Avoid duplicates by setting
|
|
* last_send on each client to send_counter, a nonce which is updated
|
|
* by sendit() above.
|
|
*/
|
|
|
|
static int
|
|
send_to_dest(ZNotice_t *notice,
|
|
int auth,
|
|
Destination *dest,
|
|
int send_counter,
|
|
int external)
|
|
{
|
|
Client **clientp;
|
|
int any = 0;
|
|
|
|
clientp = triplet_lookup(dest);
|
|
if (!clientp)
|
|
return 0;
|
|
|
|
for (; *clientp; clientp++) {
|
|
if ((*clientp)->last_send == send_counter)
|
|
continue;
|
|
(*clientp)->last_send = send_counter;
|
|
if ((*clientp)->realm) {
|
|
if (external) {
|
|
realm_handoff(notice, auth, &clientp[0]->addr, clientp[0]->realm,
|
|
1);
|
|
any = 1;
|
|
}
|
|
} else {
|
|
xmit(notice, &((*clientp)->addr), auth, *clientp);
|
|
any = 1;
|
|
}
|
|
}
|
|
|
|
return any;
|
|
}
|
|
|
|
/*
|
|
* Release anything destined for the client in the not-yet-acked table.
|
|
*/
|
|
|
|
void
|
|
nack_release(Client *client)
|
|
{
|
|
int i;
|
|
Unacked *nacked, *next;
|
|
|
|
for (i = 0; i < NACKTAB_HASHSIZE; i++) {
|
|
for (nacked = nacktab[i]; nacked; nacked = next) {
|
|
next = nacked->next;
|
|
if (nacked->client == client) {
|
|
timer_reset(nacked->timer);
|
|
Unacked_delete(nacked);
|
|
free(nacked->packet);
|
|
free(nacked);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send one packet of a fragmented message to a client. After transmitting,
|
|
* put it onto the not ack'ed list.
|
|
*/
|
|
|
|
/* the arguments must be the same as the arguments to Z_XmitFragment */
|
|
/*ARGSUSED*/
|
|
Code_t
|
|
xmit_frag(ZNotice_t *notice,
|
|
char *buf,
|
|
int len,
|
|
int waitforack)
|
|
{
|
|
struct sockaddr_in sin;
|
|
char *savebuf;
|
|
Unacked *nacked;
|
|
Code_t retval;
|
|
int sendfail = 0;
|
|
|
|
retval = ZSendPacket(buf, len, 0);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "xmit_frag send: %s", error_message(retval));
|
|
if (retval != EAGAIN && retval != ENOBUFS)
|
|
return retval;
|
|
sendfail = 1;
|
|
}
|
|
|
|
/* now we've sent it, mark it as not ack'ed */
|
|
nacked = (Unacked *) malloc(sizeof(Unacked));
|
|
if (!nacked) {
|
|
/* no space: just punt */
|
|
syslog(LOG_WARNING, "xmit_frag nack malloc");
|
|
return ENOMEM;
|
|
}
|
|
|
|
savebuf = (char *) malloc(len);
|
|
if (!savebuf) {
|
|
/* no space: just punt */
|
|
syslog(LOG_WARNING, "xmit_frag pack malloc");
|
|
free(nacked);
|
|
return ENOMEM;
|
|
}
|
|
|
|
memcpy(savebuf, buf, len);
|
|
|
|
sin = ZGetDestAddr();
|
|
nacked->client = NULL;
|
|
nacked->rexmits = (sendfail) ? -1 : 0;
|
|
nacked->packet = savebuf;
|
|
nacked->dest.addr = sin;
|
|
nacked->packsz = len;
|
|
nacked->uid = notice->z_uid;
|
|
nacked->timer = timer_set_rel(rexmit_times[0], rexmit, nacked);
|
|
Unacked_insert(&nacktab[nacktab_hashval(sin, nacked->uid)], nacked);
|
|
return(ZERR_NONE);
|
|
}
|
|
|
|
|
|
/*
|
|
* Send the notice to the client. After transmitting, put it onto the
|
|
* not ack'ed list.
|
|
*/
|
|
|
|
void
|
|
xmit(ZNotice_t *notice,
|
|
struct sockaddr_in *dest,
|
|
int auth,
|
|
Client *client)
|
|
{
|
|
char *noticepack;
|
|
Unacked *nacked;
|
|
int packlen, sendfail = 0;
|
|
Code_t retval;
|
|
|
|
noticepack = (char *) malloc(sizeof(ZPacket_t));
|
|
if (!noticepack) {
|
|
syslog(LOG_ERR, "xmit malloc");
|
|
return; /* DON'T put on nack list */
|
|
}
|
|
|
|
packlen = sizeof(ZPacket_t);
|
|
|
|
if (auth && client) {
|
|
/* we are distributing authentic and we have a pointer to auth info */
|
|
#if defined(HAVE_KRB5)
|
|
retval = ZFormatAuthenticNoticeV5(notice, noticepack, packlen,
|
|
&packlen, client->session_keyblock);
|
|
#elif defined(HAVE_KRB4)
|
|
retval = ZFormatAuthenticNotice(notice, noticepack, packlen,
|
|
&packlen, client->session_key);
|
|
#else /* !HAVE_KRB4 */
|
|
notice->z_auth = 1;
|
|
retval = ZFormatSmallRawNotice(notice, noticepack, &packlen);
|
|
#endif /* HAVE_KRB4 */
|
|
if (retval != ZERR_NONE)
|
|
syslog(LOG_ERR, "xmit auth/raw format: %s", error_message(retval));
|
|
} else {
|
|
notice->z_auth = 0;
|
|
notice->z_authent_len = 0;
|
|
notice->z_ascii_authent = (char *)"";
|
|
retval = ZFormatSmallRawNotice(notice, noticepack, &packlen);
|
|
/* This code is needed because a Zephyr can "grow" when a remote
|
|
* realm name is inserted into the Zephyr before being resent out
|
|
* locally. It essentially matches the code in realm.c to do the
|
|
* same thing with authentic Zephyrs.
|
|
*/
|
|
if (retval == ZERR_PKTLEN) {
|
|
char multi[64];
|
|
int hdrlen, message_len;
|
|
ZPacket_t the_packet;
|
|
char *buffer = (char *)&the_packet;
|
|
ZNotice_t partnotice = *notice;
|
|
int offset = 0;
|
|
int fragsize = 0;
|
|
int origoffset = 0;
|
|
int origlen = notice->z_message_len;
|
|
|
|
free(noticepack);
|
|
|
|
retval = ZSetDestAddr(dest);
|
|
if (retval) {
|
|
syslog(LOG_WARNING, "xmit: ZSetDestAddr: %s", error_message(retval));
|
|
return;
|
|
}
|
|
|
|
partnotice.z_auth = 0;
|
|
partnotice.z_authent_len = 0;
|
|
partnotice.z_ascii_authent = (char *)"";
|
|
|
|
retval = Z_FormatRawHeader(&partnotice, buffer, sizeof(ZPacket_t),
|
|
&hdrlen, NULL, NULL);
|
|
if (retval) {
|
|
syslog(LOG_ERR, "xmit unauth refrag: Z_FormatRawHeader: %s",
|
|
error_message(retval));
|
|
return;
|
|
}
|
|
|
|
if (notice->z_multinotice && strcmp(notice->z_multinotice, "")) {
|
|
if (sscanf(notice->z_multinotice, "%d/%d",
|
|
&origoffset, &origlen) != 2) {
|
|
syslog(LOG_WARNING,
|
|
"xmit unauth refrag: multinotice parse failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
fragsize = Z_MAXPKTLEN - hdrlen - Z_FRAGFUDGE;
|
|
|
|
if (fragsize < 0) {
|
|
syslog(LOG_ERR,
|
|
"xmit unauth refrag: negative fragsize, dropping packet");
|
|
return;
|
|
}
|
|
|
|
while (offset < notice->z_message_len || !notice->z_message_len) {
|
|
(void) sprintf(multi, "%d/%d", offset+origoffset, origlen);
|
|
partnotice.z_multinotice = multi;
|
|
if (offset > 0) {
|
|
(void) Z_gettimeofday(&partnotice.z_uid.tv, (struct timezone *)0);
|
|
partnotice.z_uid.tv.tv_sec = htonl((u_long)
|
|
partnotice.z_uid.tv.tv_sec);
|
|
partnotice.z_uid.tv.tv_usec = htonl((u_long)
|
|
partnotice.z_uid.tv.tv_usec);
|
|
(void) memcpy((char *)&partnotice.z_uid.zuid_addr, &__My_addr,
|
|
sizeof(__My_addr));
|
|
partnotice.z_sender_sockaddr.ip4.sin_family = AF_INET; /* XXX */
|
|
(void) memcpy((char *)&partnotice.z_sender_sockaddr.ip4.sin_addr,
|
|
&__My_addr, sizeof(__My_addr));
|
|
}
|
|
partnotice.z_message = notice->z_message + offset;
|
|
message_len = min(notice->z_message_len-offset, fragsize);
|
|
partnotice.z_message_len = message_len;
|
|
|
|
retval = Z_FormatRawHeader(&partnotice, buffer, sizeof(ZPacket_t),
|
|
&hdrlen, NULL, NULL);
|
|
if (retval) {
|
|
syslog(LOG_WARNING, "xmit unauth refrag: Z_FormatRawHeader: %s",
|
|
error_message(retval));
|
|
return;
|
|
}
|
|
|
|
(void) memcpy(buffer + hdrlen, partnotice.z_message,
|
|
partnotice.z_message_len);
|
|
|
|
xmit_frag(&partnotice, buffer, hdrlen + partnotice.z_message_len, 0);
|
|
|
|
offset += fragsize;
|
|
|
|
if (!notice->z_message_len)
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
/* End of refrag code */
|
|
|
|
if (retval)
|
|
syslog(LOG_ERR, "xmit unauth: ZFormatSmallRawNotice: %s",
|
|
error_message(retval));
|
|
}
|
|
if (!retval) {
|
|
retval = ZSetDestAddr(dest);
|
|
if (retval)
|
|
syslog(LOG_WARNING, "xmit: ZSetDestAddr: %s", error_message(retval));
|
|
}
|
|
if (!retval) {
|
|
retval = ZSendPacket(noticepack, packlen, 0);
|
|
if (retval) {
|
|
syslog(LOG_WARNING, "xmit: ZSendPacket: (%s/%d) %s",
|
|
inet_ntoa(dest->sin_addr), ntohs(dest->sin_port),
|
|
error_message(retval));
|
|
if (retval == EAGAIN || retval == ENOBUFS) {
|
|
retval = ZERR_NONE;
|
|
sendfail = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* now we've sent it, mark it as not ack'ed */
|
|
if (!retval) {
|
|
nacked = (Unacked *) malloc(sizeof(Unacked));
|
|
if (!nacked) {
|
|
/* no space: just punt */
|
|
syslog(LOG_WARNING, "xmit nack malloc");
|
|
retval = ENOMEM;
|
|
}
|
|
}
|
|
|
|
if (!retval) {
|
|
nacked->client = client;
|
|
nacked->rexmits = (sendfail) ? -1 : 0;
|
|
nacked->packet = noticepack;
|
|
nacked->dest.addr = *dest;
|
|
nacked->packsz = packlen;
|
|
nacked->uid = notice->z_uid;
|
|
nacked->timer = timer_set_rel(rexmit_times[0], rexmit, nacked);
|
|
Unacked_insert(&nacktab[nacktab_hashval(*dest, nacked->uid)], nacked);
|
|
}
|
|
if (retval)
|
|
free(noticepack);
|
|
}
|
|
|
|
/*
|
|
* Retransmit the packet specified. If we have timed out or retransmitted
|
|
* too many times, punt the packet and initiate the host recovery algorithm
|
|
* Else, increment the count and re-send the notice packet.
|
|
*/
|
|
|
|
void
|
|
rexmit(void *arg)
|
|
{
|
|
Unacked *nacked = (Unacked *) arg;
|
|
int retval;
|
|
|
|
syslog(LOG_DEBUG, "rexmit %s/%d #%d time %d",
|
|
inet_ntoa(nacked->dest.addr.sin_addr),
|
|
ntohs(nacked->dest.addr.sin_port), nacked->rexmits + 1, (int)NOW);
|
|
|
|
nacked->rexmits++;
|
|
if (rexmit_times[nacked->rexmits] == -1) {
|
|
if (!nacked->client
|
|
|| NOW - nacked->client->last_ack >= CLIENT_GIVEUP_MIN) {
|
|
/* The client (if there was one) has been unresponsive.
|
|
* Give up sending this packet, and kill the client if
|
|
* there was one. (Make sure to remove nacked from the
|
|
* nack list before calling client_deregister(), which
|
|
* scans the nack list.)
|
|
*/
|
|
Unacked_delete(nacked);
|
|
if (nacked->client) {
|
|
server_kill_clt(nacked->client);
|
|
client_deregister(nacked->client, 1);
|
|
}
|
|
free(nacked->packet);
|
|
free(nacked);
|
|
return;
|
|
} else {
|
|
/* The client has sent us an ack recently. Retry with the maximum
|
|
* retransmit time. */
|
|
nacked->rexmits--;
|
|
}
|
|
}
|
|
|
|
/* retransmit the packet */
|
|
retval = ZSetDestAddr(&nacked->dest.addr);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "rexmit set addr: %s", error_message(retval));
|
|
} else {
|
|
retval = ZSendPacket(nacked->packet, nacked->packsz, 0);
|
|
if (retval != ZERR_NONE)
|
|
syslog(LOG_WARNING, "rexmit xmit: %s", error_message(retval));
|
|
if (retval == EAGAIN || retval == ENOBUFS)
|
|
nacked->rexmits--;
|
|
}
|
|
|
|
/* reset the timer */
|
|
nacked->timer = timer_set_rel(rexmit_times[nacked->rexmits], rexmit,
|
|
nacked);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Send an acknowledgement to the sending client, by sending back the
|
|
* header from the original notice with the z_kind field changed to either
|
|
* SERVACK or SERVNAK, and the contents of the message either SENT or
|
|
* NOT_SENT, depending on the value of the sent argument.
|
|
*/
|
|
|
|
void
|
|
clt_ack(ZNotice_t *notice,
|
|
struct sockaddr_in *who,
|
|
Sent_type sent)
|
|
{
|
|
ZNotice_t acknotice;
|
|
ZPacket_t ackpack;
|
|
int packlen;
|
|
Code_t retval;
|
|
|
|
if (bdumping) { /* don't ack while dumping */
|
|
zdbug((LOG_DEBUG,"bdumping, no ack"));
|
|
return;
|
|
}
|
|
|
|
acknotice = *notice;
|
|
|
|
acknotice.z_kind = SERVACK;
|
|
switch (sent) {
|
|
case SENT:
|
|
acknotice.z_message = ZSRVACK_SENT;
|
|
break;
|
|
case NOT_FOUND:
|
|
acknotice.z_message = ZSRVACK_FAIL;
|
|
acknotice.z_kind = SERVNAK;
|
|
break;
|
|
case AUTH_FAILED:
|
|
acknotice.z_kind = SERVNAK;
|
|
acknotice.z_message = ZSRVACK_NOTSENT;
|
|
break;
|
|
case NOT_SENT:
|
|
acknotice.z_message = ZSRVACK_NOTSENT;
|
|
break;
|
|
default:
|
|
abort ();
|
|
}
|
|
|
|
acknotice.z_multinotice = "";
|
|
|
|
/* leave room for the trailing null */
|
|
acknotice.z_message_len = strlen(acknotice.z_message) + 1;
|
|
|
|
packlen = sizeof(ackpack);
|
|
|
|
retval = ZFormatSmallRawNotice(&acknotice, ackpack, &packlen);
|
|
|
|
if (retval == ZERR_HEADERLEN) {
|
|
/* Since an ack header can be larger than a message header... (crock) */
|
|
acknotice.z_opcode = "";
|
|
acknotice.z_class = "";
|
|
acknotice.z_class_inst = "";
|
|
acknotice.z_opcode = "";
|
|
acknotice.z_default_format = "";
|
|
|
|
retval = ZFormatSmallRawNotice(&acknotice, ackpack, &packlen);
|
|
}
|
|
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_ERR, "clt_ack format: %s", error_message(retval));
|
|
return;
|
|
}
|
|
retval = ZSetDestAddr(who);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "clt_ack set addr: %s", error_message(retval));
|
|
return;
|
|
}
|
|
retval = ZSendPacket(ackpack, packlen, 0);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "clt_ack xmit: %s", error_message(retval));
|
|
return;
|
|
} else {
|
|
zdbug((LOG_DEBUG, "packet sent"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* An ack has arrived.
|
|
* remove the packet matching this notice from the not-yet-acked queue
|
|
*/
|
|
|
|
static void
|
|
nack_cancel(ZNotice_t *notice,
|
|
struct sockaddr_in *who)
|
|
{
|
|
Unacked *nacked;
|
|
int hashval;
|
|
|
|
/* search the not-yet-acked table for this packet, and flush it. */
|
|
hashval = nacktab_hashval(*who, notice->z_uid);
|
|
for (nacked = nacktab[hashval]; nacked; nacked = nacked->next) {
|
|
if (nacked->dest.addr.sin_addr.s_addr == who->sin_addr.s_addr
|
|
&& nacked->dest.addr.sin_port == who->sin_port
|
|
&& ZCompareUID(&nacked->uid, ¬ice->z_uid)) {
|
|
if (nacked->client)
|
|
nacked->client->last_ack = NOW;
|
|
timer_reset(nacked->timer);
|
|
free(nacked->packet);
|
|
Unacked_delete(nacked);
|
|
free(nacked);
|
|
return;
|
|
}
|
|
}
|
|
|
|
zdbug((LOG_DEBUG,"nack_cancel: nack not found %s:%08X,%08X",
|
|
inet_ntoa (notice->z_uid.zuid_addr),
|
|
notice->z_uid.tv.tv_sec, notice->z_uid.tv.tv_usec));
|
|
}
|
|
|
|
/* Dispatch an HM_CTL notice. */
|
|
|
|
Code_t
|
|
hostm_dispatch(ZNotice_t *notice,
|
|
int auth,
|
|
struct sockaddr_in *who,
|
|
Server *server)
|
|
{
|
|
char *opcode = notice->z_opcode;
|
|
int i, add_it = 0, remove_it = 0;
|
|
|
|
if (notice->z_kind == HMACK) {
|
|
/* Ignore. */
|
|
;
|
|
} else if (notice->z_kind != HMCTL) {
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
} else if (strcmp(opcode, HM_FLUSH) == 0) {
|
|
client_flush_host(&who->sin_addr);
|
|
if (server == me_server)
|
|
server_forward(notice, auth, who);
|
|
} else if (strcmp(opcode, HM_BOOT) == 0) {
|
|
client_flush_host(&who->sin_addr);
|
|
if (server == me_server) {
|
|
server_forward(notice, auth, who);
|
|
ack(notice, who);
|
|
add_it = 1;
|
|
}
|
|
} else if (strcmp(opcode, HM_ATTACH) == 0) {
|
|
if (server == me_server) {
|
|
server_forward(notice, auth, who);
|
|
ack(notice, who);
|
|
add_it = 1;
|
|
} else {
|
|
remove_it = 1;
|
|
}
|
|
} else if (strcmp(opcode, HM_DETACH) == 0) {
|
|
remove_it = 1;
|
|
} else {
|
|
syslog(LOG_WARNING, "hm_dispatch: unknown opcode %s", opcode);
|
|
}
|
|
|
|
if (add_it) {
|
|
for (i = 0; i < num_hosts; i++) {
|
|
if (hosts[i].s_addr == who->sin_addr.s_addr)
|
|
break;
|
|
}
|
|
if (i == num_hosts) {
|
|
if (hosts_size == 0) {
|
|
hosts = (struct in_addr *) malloc(HOSTS_SIZE_INIT *
|
|
sizeof(struct in_addr));
|
|
if (!hosts)
|
|
return ENOMEM;
|
|
hosts_size = HOSTS_SIZE_INIT;
|
|
} else if (num_hosts == hosts_size) {
|
|
hosts = (struct in_addr *) realloc(hosts, hosts_size * 2 *
|
|
sizeof(struct in_addr));
|
|
if (!hosts)
|
|
return ENOMEM;
|
|
hosts_size *= 2;
|
|
}
|
|
hosts[num_hosts++] = who->sin_addr;
|
|
}
|
|
} else if (remove_it) {
|
|
for (i = 0; i < num_hosts; i++) {
|
|
if (hosts[i].s_addr == who->sin_addr.s_addr) {
|
|
memmove(&hosts[i], &hosts[i + 1], num_hosts - (i + 1));
|
|
num_hosts--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
/*
|
|
* Dispatch a ZEPHYR_CTL notice.
|
|
*/
|
|
|
|
Code_t
|
|
control_dispatch(ZNotice_t *notice,
|
|
int auth,
|
|
struct sockaddr_in *who,
|
|
Server *server)
|
|
{
|
|
char *target, *opcode = notice->z_opcode;
|
|
Client *client;
|
|
Code_t retval;
|
|
int wantdefs;
|
|
ZRealm *realm;
|
|
struct sockaddr_in newwho;
|
|
|
|
/*
|
|
* ZEPHYR_CTL Opcodes expected are:
|
|
* BOOT (inst HM): host has booted; flush data.
|
|
* CLIENT_SUBSCRIBE: process with the subscription mananger.
|
|
* CLIENT_UNSUBSCRIBE: ""
|
|
* CLIENT_CANCELSUB: ""
|
|
*/
|
|
|
|
zdbug((LOG_DEBUG, "ctl_disp: opc=%s", opcode));
|
|
|
|
notice_extract_address(notice, &newwho);
|
|
realm = realm_which_realm(&newwho);
|
|
if (realm)
|
|
return(realm_control_dispatch(notice, auth, who, server, realm));
|
|
|
|
if (strcasecmp(notice->z_class_inst, ZEPHYR_CTL_HM) == 0) {
|
|
return hostm_dispatch(notice, auth, who, server);
|
|
} else if (strcmp(opcode, CLIENT_GIMMESUBS) == 0 ||
|
|
strcmp(opcode, CLIENT_GIMMEDEFS) == 0) {
|
|
/* this special case is before the auth check so that
|
|
someone who has no subscriptions does NOT get a SERVNAK
|
|
but rather an empty list. Note we must therefore
|
|
check authentication inside subscr_sendlist */
|
|
ack(notice, who);
|
|
subscr_sendlist(notice, auth, who);
|
|
return ZERR_NONE;
|
|
} else if (!auth) {
|
|
syslog(LOG_INFO, "unauthenticated %s message purportedly from %s",
|
|
opcode, notice->z_sender);
|
|
if (server == me_server)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
wantdefs = strcmp(opcode, CLIENT_SUBSCRIBE_NODEFS);
|
|
if (!wantdefs || strcmp(opcode, CLIENT_SUBSCRIBE) == 0) {
|
|
/* subscription notice */
|
|
retval = client_register(notice, &who->sin_addr, &client, wantdefs);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_NOTICE, "subscr %s/%s/%d failed: %s",
|
|
notice->z_sender, inet_ntoa(who->sin_addr),
|
|
ntohs(notice->z_port), error_message(retval));
|
|
if (server == me_server) {
|
|
if (retval == ZSRV_BADSUBPORT)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
else
|
|
nack(notice, who);
|
|
}
|
|
return(ZERR_NONE);
|
|
}
|
|
if (strcmp(client->principal->string, notice->z_sender) != 0) {
|
|
/* you may only subscribe for your own clients */
|
|
syslog(LOG_NOTICE,
|
|
"subscr request from %s on port used by %s (%s.%d)",
|
|
notice->z_sender, client->principal->string,
|
|
inet_ntoa(who->sin_addr), ntohs(notice->z_port)); /* XXX */
|
|
if (server == me_server)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
return ZERR_NONE;
|
|
}
|
|
#ifdef HAVE_KRB5
|
|
if (client->session_keyblock) {
|
|
krb5_free_keyblock_contents(Z_krb5_ctx, client->session_keyblock);
|
|
retval = krb5_copy_keyblock_contents(Z_krb5_ctx, ZGetSession(),
|
|
client->session_keyblock);
|
|
} else {
|
|
retval = krb5_copy_keyblock(Z_krb5_ctx, ZGetSession(),
|
|
&client->session_keyblock);
|
|
}
|
|
if (retval) {
|
|
syslog(LOG_WARNING, "keyblock copy failed in subscr: %s",
|
|
error_message(retval));
|
|
if (server == me_server)
|
|
nack(notice, who);
|
|
return ZERR_NONE;
|
|
}
|
|
#else
|
|
#ifdef HAVE_KRB4
|
|
/* in case it's changed */
|
|
memcpy(client->session_key, ZGetSession(), sizeof(C_Block));
|
|
#endif
|
|
#endif
|
|
retval = subscr_subscribe(client, notice, server);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "subscr failed: %s", error_message(retval));
|
|
if (server == me_server)
|
|
nack(notice, who);
|
|
return ZERR_NONE;
|
|
}
|
|
} else if (strcmp(opcode, CLIENT_UNSUBSCRIBE) == 0) {
|
|
client = client_find(&who->sin_addr, notice->z_port);
|
|
if (client != NULL) {
|
|
if (strcmp(client->principal->string, notice->z_sender) != 0) {
|
|
/* you may only cancel for your own clients */
|
|
syslog(LOG_NOTICE,
|
|
"unsubscr request from %s on port used by %s (%s.%d)",
|
|
notice->z_sender, client->principal->string,
|
|
inet_ntoa(who->sin_addr), ntohs(notice->z_port)); /*XXX*/
|
|
if (server == me_server)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
return ZERR_NONE;
|
|
}
|
|
subscr_cancel(who, notice);
|
|
} else {
|
|
nack(notice, who);
|
|
return ZERR_NONE;
|
|
}
|
|
} else if (strcmp(opcode, CLIENT_CANCELSUB) == 0) {
|
|
/* canceling subscriptions implies I can punt info about this client */
|
|
client = client_find(&who->sin_addr, notice->z_port);
|
|
if (client == NULL) {
|
|
if (server == me_server)
|
|
nack(notice, who);
|
|
return ZERR_NONE;
|
|
}
|
|
if (strcmp(client->principal->string, notice->z_sender) != 0) {
|
|
/* you may only cancel for your own clients */
|
|
syslog(LOG_NOTICE,
|
|
"cancel request from %s on port used by %s (%s.%d)",
|
|
notice->z_sender, client->principal->string,
|
|
inet_ntoa(who->sin_addr), ntohs(notice->z_port)); /* XXX */
|
|
if (server == me_server)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
return ZERR_NONE;
|
|
}
|
|
/* don't flush locations here, let him do it explicitly */
|
|
client_deregister(client, 0);
|
|
} else if (strcmp(opcode, CLIENT_FLUSHSUBS) == 0) {
|
|
target = notice->z_sender;
|
|
if (notice->z_message_len > 0 && *notice->z_message != 0) {
|
|
target = notice->z_message;
|
|
if (memchr(target, '\0', notice->z_message_len) == NULL) {
|
|
syslog(LOG_WARNING, "malformed flushsubs");
|
|
if (server == me_server)
|
|
nack(notice, who);
|
|
return ZERR_NONE;
|
|
}
|
|
if (strcmp(target, notice->z_sender) != 0 &&
|
|
!opstaff_check(notice->z_sender)) {
|
|
syslog(LOG_NOTICE, "unauth flushsubs for %s by %s",
|
|
target, notice->z_sender);
|
|
if (server == me_server)
|
|
clt_ack(notice, who, AUTH_FAILED);
|
|
return ZERR_NONE;
|
|
}
|
|
}
|
|
client_flush_princ(target);
|
|
} else {
|
|
syslog(LOG_WARNING, "unknown ctl opcode %s", opcode);
|
|
if (server == me_server) {
|
|
if (strcmp(notice->z_class_inst, ZEPHYR_CTL_REALM) != 0)
|
|
nack(notice, who);
|
|
}
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
if (server == me_server) {
|
|
ack(notice, who);
|
|
server_forward(notice, auth, who);
|
|
}
|
|
return ZERR_NONE;
|
|
}
|
|
|
|
void
|
|
hostm_shutdown(void)
|
|
{
|
|
int i, s, newserver;
|
|
struct sockaddr_in sin;
|
|
|
|
for (i = 0; i < nservers; i++) {
|
|
if (i != me_server_idx && otherservers[i].state == SERV_UP)
|
|
break;
|
|
}
|
|
newserver = (i < nservers);
|
|
sin.sin_family = AF_INET;
|
|
for (i = 0; i < num_hosts; i++) {
|
|
sin.sin_addr = hosts[i];
|
|
sin.sin_port = hm_port;
|
|
if (newserver) {
|
|
while (1) {
|
|
s = (random() % (nservers - 1)) + 1;
|
|
if (otherservers[s].state == SERV_UP)
|
|
break;
|
|
}
|
|
hostm_deathgram(&sin, &otherservers[s]);
|
|
} else {
|
|
hostm_deathgram(&sin, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
realm_shutdown(void)
|
|
{
|
|
int i, s, newserver;
|
|
|
|
for (i = 0; i < nservers; i++) {
|
|
if (i != me_server_idx && otherservers[i].state == SERV_UP)
|
|
break;
|
|
}
|
|
zdbug((LOG_DEBUG, "rlm_shutdown"));
|
|
|
|
newserver = (i < nservers);
|
|
if (newserver) {
|
|
while (1) {
|
|
s = (random() % (nservers - 1)) + 1;
|
|
if (otherservers[s].state == SERV_UP)
|
|
break;
|
|
}
|
|
realm_deathgram(&otherservers[s]);
|
|
} else {
|
|
realm_deathgram(NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
hostm_deathgram(struct sockaddr_in *sin,
|
|
Server *server)
|
|
{
|
|
Code_t retval;
|
|
int shutlen;
|
|
ZNotice_t shutnotice;
|
|
char *shutpack;
|
|
|
|
memset (&shutnotice, 0, sizeof(shutnotice));
|
|
|
|
shutnotice.z_kind = HMCTL;
|
|
shutnotice.z_port = sin->sin_port; /* we are sending it */
|
|
shutnotice.z_class = HM_CTL_CLASS;
|
|
shutnotice.z_class_inst = HM_CTL_SERVER;
|
|
shutnotice.z_opcode = SERVER_SHUTDOWN;
|
|
shutnotice.z_sender = HM_CTL_SERVER;
|
|
shutnotice.z_recipient = hm_recipient();
|
|
shutnotice.z_default_format = "";
|
|
shutnotice.z_num_other_fields = 0;
|
|
shutnotice.z_message = (server) ? server->addr_str : NULL;
|
|
shutnotice.z_message_len = (server) ? strlen(server->addr_str) + 1 : 0;
|
|
|
|
retval = ZFormatNotice(&shutnotice, &shutpack, &shutlen, ZNOAUTH);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_ERR, "hm_shut format: %s",error_message(retval));
|
|
return;
|
|
}
|
|
retval = ZSetDestAddr(sin);
|
|
if (retval != ZERR_NONE) {
|
|
syslog(LOG_WARNING, "hm_shut set addr: %s", error_message(retval));
|
|
free(shutpack);
|
|
return;
|
|
}
|
|
retval = ZSendPacket(shutpack, shutlen, 0);
|
|
if (retval != ZERR_NONE)
|
|
syslog(LOG_WARNING, "hm_shut xmit: %s", error_message(retval));
|
|
free(shutpack);
|
|
}
|
|
|
|
static char *
|
|
hm_recipient(void)
|
|
{
|
|
static char *recipient;
|
|
char *realm;
|
|
|
|
if (recipient)
|
|
return recipient;
|
|
|
|
realm = (char *)ZGetRealm();
|
|
if (!realm)
|
|
realm = "???";
|
|
recipient = (char *) malloc(strlen(realm) + 4);
|
|
strcpy (recipient, "hm@");
|
|
strcat (recipient, realm);
|
|
return recipient;
|
|
}
|