/* This file is part of the Project Athena Zephyr Notification System. * It contains functions for dumping server state between servers. * * Created by: John T. Kohl * * $Source$ * $Id$ * $Author$ * * Copyright (c) 1987,1988,1991 by the Massachusetts Institute of Technology. * For copying and distribution information, see the file * "mit-copyright.h". */ #include #include "zserver.h" #include #ifndef lint static const char rcsid_bdump_c[] = "$Id$"; #endif /* lint */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif /* * External functions are: * * void bdump_offer(who) * strut sockaddr_in *who; * * void bdump_send() * * void bdump_get(notice, auth, who, server) * ZNotice_t *notice; * int auth; * struct sockaddr_in *who; * Server *server; * * Code_t bdump_send_list_tcp(kind, port, class, inst, opcode, * sender, recip, lyst, num) * ZNotice_Kind_t kind; * u_short port; * char *class, *inst, *opcode, *sender, *recip; * char *lyst[]; * int num; */ static void close_bdump(void* arg); static Code_t bdump_send_loop(Server *server); static Code_t bdump_recv_loop(Server *server); static void bdump_get_v12(ZNotice_t *, int, struct sockaddr_in *, Server *); static Code_t get_packet(char **packet, int *len, int *retlen); static Code_t extract_sin(ZNotice_t *notice, struct sockaddr_in *target); static Code_t send_done(void); static Code_t send_list(ZNotice_Kind_t kind, int port, char *class_name, char *inst, char *opcode, char *sender, char *recip, char **lyst, int num); static Code_t send_normal_tcp(ZNotice_Kind_t kind, int port, char *class_name, char *inst, char *opcode, char *sender, char *recip, char *message, int len); static int net_read(FILE *f, char *buf, int len); static int net_write(FILE *f, char *buf, int len); static int setup_file_pointers(void); static void shutdown_file_pointers(void); static void cleanup(Server *server); static Code_t transmit_tcp(char *pack, int packlen); #ifdef HAVE_KRB5 static int des_service_decrypt(unsigned char *in, unsigned char *out); #endif #ifdef HAVE_KRB5 static long ticket5_time; #define TKT5LIFETIME 8*60*60 #define tkt5_lifetime(val) (val) #endif #ifdef HAVE_KRB4 static long ticket_time; #define TKTLIFETIME 120 #define tkt_lifetime(val) ((long) val * 5L * 60L) #endif /* HAVE_KRB4 */ #if defined(HAVE_KRB4) extern C_Block serv_key; extern Sched serv_ksched; #endif #if defined(HAVE_KRB5) && !defined(HAVE_KRB4) krb5_keyblock *server_key; #endif static Timer *bdump_timer; static int live_socket = -1; static FILE *input, *output; static struct sockaddr_in bdump_sin; #ifdef HAVE_KRB5 static krb5_auth_context bdump_ac; #endif static Z_AuthProc bdump_auth_proc; int bdumping; int bdump_concurrent; /* * Functions for performing a brain dump between servers. */ /* * offer the brain dump to another server */ void bdump_offer(struct sockaddr_in *who) { Code_t retval; char buf[512], *addr, *lyst[2]; #if !defined(HAVE_KRB4) && !defined(HAVE_KRB5) int bdump_port = IPPORT_RESERVED - 1; #endif /* !HAVE_KRB4 */ zdbug((LOG_DEBUG, "bdump_offer")); #if defined(HAVE_KRB4) || defined(HAVE_KRB5) /* * when using kerberos server-server authentication, we can * use any random local address */ bdump_socket = socket(AF_INET, SOCK_STREAM, 0); if (bdump_socket < 0) { syslog(LOG_ERR,"bdump_offer: socket: %m"); bdump_socket = -1; return; } memset(&bdump_sin, 0, sizeof(bdump_sin)); /* a port field of 0 makes the UNIX * kernel choose an appropriate port/address pair */ bdump_sin.sin_port = 0; bdump_sin.sin_addr = my_addr; bdump_sin.sin_family = AF_INET; retval = bind(bdump_socket, (struct sockaddr *) &bdump_sin, sizeof(bdump_sin)); if (retval < 0) { syslog(LOG_ERR, "bdump_offer: bind: %m"); close(bdump_socket); bdump_socket = -1; return; } if (!bdump_sin.sin_port) { unsigned int len = sizeof(bdump_sin); if (getsockname(bdump_socket, (struct sockaddr *) &bdump_sin, &len) < 0) { syslog(LOG_ERR, "bdump_offer: getsockname: %m"); close(bdump_socket); bdump_socket = -1; return; } } #else /* !HAVE_KRB4 */ /* * when not using HAVE_KRB4, we can't use any old port, we use * Internet reserved ports instead (rresvport) */ bdump_socket = rresvport(&bdump_port); if (bdump_socket < 0) { syslog(LOG_ERR,"bdump_offer: socket: %m"); bdump_socket = -1; return; } memset(&bdump_sin, 0, sizeof(bdump_sin)); bdump_sin.sin_port = htons((unsigned short) bdump_port); bdump_sin.sin_addr = my_addr; bdump_sin.sin_family = AF_INET; #endif /* HAVE_KRB4 */ listen(bdump_socket, 1); bdump_timer = timer_set_rel(20L, close_bdump, NULL); FD_SET(bdump_socket, &interesting); nfds = max(bdump_socket, srv_socket) + 1; addr = inet_ntoa(bdump_sin.sin_addr); sprintf(buf, "%d", ntohs(bdump_sin.sin_port)); lyst[0] = addr; lyst[1] = buf; retval = ZSetDestAddr(who); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_offer: ZSetDestAddr: %s", error_message(retval)); return; } /* myname is the hostname */ /* the class instance is the version number, here it is */ /* bdump_version, which is set in global.c */ send_list(ACKED, srv_addr.sin_port, ZEPHYR_ADMIN_CLASS, bdump_version, ADMIN_BDUMP, myname, "", lyst, 2); zdbug((LOG_DEBUG,"bdump_offer: address is %s/%d\n", inet_ntoa(bdump_sin.sin_addr), ntohs(bdump_sin.sin_port))); return; } /* * Accept a connection, and send the brain dump to the other server */ void bdump_send(void) { struct sockaddr_in from; Server *server; Code_t retval; unsigned int fromlen = sizeof(from); int on = 1; #ifdef _POSIX_VERSION struct sigaction action; #endif #if defined(HAVE_KRB4) || defined(HAVE_KRB5) char *data = NULL; int len = 0; int proto = 0; #endif #ifdef HAVE_KRB4 KTEXT_ST ticket; AUTH_DAT kdata; /* may be moved into kstuff.c */ char instance [INST_SZ]; #endif #ifdef HAVE_KRB5 /* may be moved into kstuff.c */ krb5_principal principal; krb5_data k5data; krb5_keytab kt; #endif #if !defined(HAVE_KRB4) && !defined(HAVE_KRB5) unsigned short fromport; #endif /* HAVE_KRB4 */ zdbug((LOG_DEBUG, "bdump_send")); /* accept the connection, and send the brain dump */ live_socket = accept(bdump_socket, (struct sockaddr *) &from, &fromlen); if (live_socket < 0) { syslog(LOG_ERR,"bdump_send: accept: %m"); return; } if (setsockopt(live_socket, SOL_SOCKET, SO_KEEPALIVE, (char *) &on, sizeof(on)) < 0) syslog(LOG_WARNING, "bdump_send: setsockopt (SO_KEEPALIVE): %m"); #if !defined(HAVE_KRB4) && !defined(HAVE_KRB5) fromport = ntohs(from.sin_port); #endif #ifdef _POSIX_VERSION sigemptyset(&action.sa_mask); action.sa_flags = 0; action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_IGN); /* so we can detect failures */ #endif from.sin_port = srv_addr.sin_port; /* we don't care what port * it came from, and we need to * fake out server_which_server() */ server = server_which_server(&from); if (!server) { syslog(LOG_ERR, "bdump_send: unknown server?"); server = limbo_server; } zdbug((LOG_INFO, "bdump_send: connection from %s/%d", inet_ntoa(from.sin_addr), ntohs(from.sin_port))); bdumping = 1; server->dumping = 1; bdump_auth_proc = ZNOAUTH; if (bdump_socket >= 0) { /* shut down the listening socket and the timer. */ FD_CLR(bdump_socket, &interesting); close(bdump_socket); nfds = srv_socket + 1; bdump_socket = -1; timer_reset(bdump_timer); } /* Now begin the brain dump. */ #if defined(HAVE_KRB5) || defined(HAVE_KRB4) retval = ReadKerberosData(live_socket, &len, &data, &proto); if (retval != 0) { syslog(LOG_ERR, "bdump_send: ReadKerberosData: %s", error_message(retval)); cleanup(server); return; } syslog(LOG_DEBUG, "bdump_send: got %d bytes of authenticator for protocol %d", len, proto); if (get_tgt()) { syslog(LOG_ERR, "bdump_send: get_tgt failed"); cleanup(server); return; } switch(proto) { #ifdef HAVE_KRB5 case 5: /* "server" side */ retval = krb5_build_principal(Z_krb5_ctx, &principal, strlen(ZGetRealm()), ZGetRealm(), SERVER_KRB5_SERVICE, SERVER_INSTANCE, NULL); if (retval) { syslog(LOG_ERR, "bdump_send: krb5_build_principal: %s", error_message(retval)); cleanup(server); return; } retval = krb5_auth_con_init(Z_krb5_ctx, &bdump_ac); if (retval) { syslog(LOG_ERR, "bdump_send: krb5_auth_con_init: %s", error_message(retval)); cleanup(server); return; } retval = krb5_auth_con_setflags(Z_krb5_ctx, bdump_ac, KRB5_AUTH_CONTEXT_DO_SEQUENCE); if (retval) { syslog(LOG_ERR, "bdump_send: krb5_auth_con_setflags: %s", error_message(retval)); cleanup(server); return; } retval = krb5_auth_con_genaddrs(Z_krb5_ctx, bdump_ac, live_socket, KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR|KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR); if (retval) { syslog(LOG_ERR, "bdump_send: krb5_auth_con_genaddrs: %s", error_message(retval)); cleanup(server); return; } /* Get the "client" krb_ap_req */ memset((char *)&k5data, 0, sizeof(krb5_data)); k5data.length = len; k5data.data = data; /* resolve keytab */ retval = krb5_kt_resolve(Z_krb5_ctx, keytab_file, &kt); if (retval) { syslog(LOG_ERR, "bdump_send: cannot resolve keytab: %s", error_message(retval)); krb5_kt_close(Z_krb5_ctx, kt); cleanup(server); return; } retval = krb5_rd_req(Z_krb5_ctx, &bdump_ac, &k5data, principal, kt, NULL, NULL); krb5_free_principal(Z_krb5_ctx, principal); krb5_kt_close(Z_krb5_ctx, kt); free(k5data.data); memset((char *)&k5data, 0, sizeof(krb5_data)); if (retval) { syslog(LOG_ERR, "bdump_send: mutual authentication failed: %s", error_message(retval)); cleanup(server); return; } /* Now send back our auth packet */ retval = krb5_mk_rep(Z_krb5_ctx, bdump_ac, &k5data); if (retval) { syslog(LOG_ERR, "bdump_send: krb5_mk_rep: %s", error_message(retval)); cleanup(server); return; } retval = SendKrb5Data(live_socket, &k5data); if (retval) { syslog(LOG_ERR, "bdump_send: cannot send authenticator: %s", error_message(retval)); krb5_free_data_contents(Z_krb5_ctx, &k5data); cleanup(server); return; } krb5_free_data_contents(Z_krb5_ctx, &k5data); break; #endif /* HAVE_KRB5 */ #ifdef HAVE_KRB4 case 4: bdump_auth_proc = Z_FormatAuthHeaderWithASCIIAddress; /* here to krb_rd_req from GetKerberosData candidate for refactoring back into kstuff.c */ (void) strcpy(instance, "*"); /* let Kerberos fill it in */ ticket.length = len; memcpy(&ticket.dat, data, MIN(len, (int)sizeof(ticket.dat))); retval = krb_rd_req(&ticket, SERVER_SERVICE, instance, from.sin_addr.s_addr, &kdata, srvtab_file); /* retval = GetKerberosData(live_socket, from.sin_addr, &kdata, SERVER_SERVICE, srvtab_file); */ if (retval != KSUCCESS) { syslog(LOG_ERR, "bdump_send: getkdata: %s", error_message(retval)); cleanup(server); return; } if (strcmp(kdata.pname, SERVER_SERVICE) || strcmp(kdata.pinst, SERVER_INSTANCE) || strcmp(kdata.prealm, ZGetRealm())) { syslog(LOG_ERR, "bdump_send: peer not zephyr: %s.%s@%s", kdata.pname, kdata.pinst, kdata.prealm); cleanup(server); return; } /* authenticate back */ retval = SendKerberosData(live_socket, &ticket, SERVER_SERVICE, SERVER_INSTANCE); if (retval != 0) { syslog(LOG_ERR,"bdump_send: SendKerberosData: %s", error_message (retval)); cleanup(server); return; } break; #endif /* HAVE_KRB4 */ } #else /* HAVE_KRB4 || HAVE_KRB5 */ if (fromport > IPPORT_RESERVED || fromport < IPPORT_RESERVED / 2) { syslog(LOG_ERR, "bdump_send: bad port from peer: %d", fromport); cleanup(server); return; } #endif /* HAVE_KRB4 || HAVE_KRB5 */ retval = setup_file_pointers(); if (retval != 0) { syslog (LOG_WARNING, "bdump_send: can't set up file pointers: %s", error_message(retval)); cleanup(server); return; } retval = bdump_send_loop(server); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_send: bdump_send_loop failed: %s", error_message(retval)); cleanup(server); return; } retval = bdump_recv_loop(server); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_send: bdump_recv_loop failed: %s", error_message(retval)); cleanup(server); return; } zdbug((LOG_DEBUG, "bdump_send: finished")); if (server != limbo_server) { /* set this guy to be up, and schedule a hello */ server->state = SERV_UP; timer_reset(server->timer); server->timer = timer_set_rel(0L, server_timo, server); } shutdown_file_pointers(); #ifdef _POSIX_VERSION action.sa_handler = SIG_DFL; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_DFL); #endif bdumping = 0; server->dumping = 0; /* Now that we are finished dumping, send all the queued packets */ server_send_queue(server); return; } /*ARGSUSED*/ static void bdump_get_v12 (ZNotice_t *notice, int auth, struct sockaddr_in *who, Server *server) { struct sockaddr_in from; Code_t retval; int on = 1; #ifdef _POSIX_VERSION struct sigaction action; #endif #if defined(HAVE_KRB4) || defined(HAVE_KRB5) #ifdef HAVE_KRB5 krb5_creds creds; krb5_creds *credsp; krb5_principal principal; krb5_data data; krb5_ap_rep_enc_part *rep; #endif #ifdef HAVE_KRB4 KTEXT_ST ticket; AUTH_DAT kdata; #endif #else /* !HAVE_KRB4 && !HAVE_KRB5 */ int reserved_port = IPPORT_RESERVED - 1; #endif /* !HAVE_KRB4 && !HAVE_KRB5 */ bdumping = 1; server->dumping = 1; bdump_auth_proc = ZNOAUTH; #ifdef _POSIX_VERSION action.sa_flags = 0; sigemptyset(&action.sa_mask); action.sa_handler = SIG_IGN; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_IGN); /* so we can detect problems */ #endif /* _POSIX_VRESION */ if (bdump_socket >= 0) { /* We cannot go get a brain dump when someone may potentially be connecting to us (if that other server is the server to whom we are connecting, we will deadlock. so we shut down the listening socket and the timer. */ FD_CLR(bdump_socket, &interesting); close(bdump_socket); nfds = srv_socket+1; bdump_socket = -1; timer_reset(bdump_timer); } retval = extract_sin(notice, &from); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_get: sin: %s", error_message(retval)); #ifdef _POSIX_VERSION action.sa_handler = SIG_DFL; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_DFL); #endif bdumping = 0; server->dumping = 0; return; } #if !defined(HAVE_KRB4) && !defined(HAVE_KRB5) if (ntohs(from.sin_port) > IPPORT_RESERVED || ntohs(from.sin_port) < IPPORT_RESERVED / 2) { syslog(LOG_ERR, "bdump_get: port not reserved: %d", ntohs(from.sin_port)); cleanup(server); return; } live_socket = rresvport(&reserved_port); #else /* !HAVE_KRB4 && !HAVE_KRB5 */ live_socket = socket(AF_INET, SOCK_STREAM, 0); #endif /* !HAVE_KRB4 && !HAVE_KRB5 */ if (live_socket < 0) { syslog(LOG_ERR, "bdump_get: socket: %m"); cleanup(server); return; } if (connect(live_socket, (struct sockaddr *) &from, sizeof(from))) { syslog(LOG_ERR, "bdump_get: connect: %m"); cleanup(server); return; } if (setsockopt(live_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on)) < 0) syslog(LOG_WARNING, "bdump_get: setsockopt (SO_KEEPALIVE): %m"); zdbug((LOG_DEBUG, "bdump_get: connected")); /* Now begin the brain dump. */ #if defined(HAVE_KRB4) || defined(HAVE_KRB5) if (get_tgt()) { syslog(LOG_ERR, "bdump_get: get_tgt failed"); cleanup(server); return; } switch(bdump_auth_proto) { #ifdef HAVE_KRB5 case 5: /* "client" side */ memset((char *)&creds, 0, sizeof(creds)); retval = krb5_build_principal(Z_krb5_ctx, &principal, strlen(ZGetRealm()), ZGetRealm(), SERVER_KRB5_SERVICE, SERVER_INSTANCE, NULL); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_build_principal: %s", error_message(retval)); cleanup(server); return; } retval = krb5_copy_principal(Z_krb5_ctx, principal, &creds.server); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_copy_principal (server): %s", error_message(retval)); krb5_free_principal(Z_krb5_ctx, principal); cleanup(server); return; } retval = krb5_copy_principal(Z_krb5_ctx, principal, &creds.client); krb5_free_principal(Z_krb5_ctx, principal); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_copy_principal (client): %s", error_message(retval)); krb5_free_cred_contents(Z_krb5_ctx, &creds); cleanup(server); return; } retval = krb5_get_credentials(Z_krb5_ctx, 0, Z_krb5_ccache, &creds, &credsp); krb5_free_cred_contents(Z_krb5_ctx, &creds); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_get_credentials: %s", error_message(retval)); cleanup(server); return; } retval = krb5_auth_con_init(Z_krb5_ctx, &bdump_ac); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_auth_con_init: %s", error_message(retval)); krb5_free_creds(Z_krb5_ctx, credsp); cleanup(server); return; } retval = krb5_auth_con_setflags(Z_krb5_ctx, bdump_ac, KRB5_AUTH_CONTEXT_DO_SEQUENCE); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_auth_con_setflags: %s", error_message(retval)); krb5_free_creds(Z_krb5_ctx, credsp); cleanup(server); return; } retval = krb5_auth_con_genaddrs(Z_krb5_ctx, bdump_ac, live_socket, KRB5_AUTH_CONTEXT_GENERATE_LOCAL_FULL_ADDR|KRB5_AUTH_CONTEXT_GENERATE_REMOTE_FULL_ADDR); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_auth_con_genaddrs: %s", error_message(retval)); krb5_free_creds(Z_krb5_ctx, credsp); cleanup(server); return; } memset((char *)&data, 0, sizeof(krb5_data)); retval = krb5_mk_req_extended(Z_krb5_ctx, &bdump_ac, AP_OPTS_MUTUAL_REQUIRED|AP_OPTS_USE_SUBKEY, NULL, credsp, &data); if (retval) { syslog(LOG_ERR, "bdump_get: krb5_mk_req_ext: %s", error_message(retval)); krb5_free_creds(Z_krb5_ctx, credsp); cleanup(server); return; } retval = SendKrb5Data(live_socket, &data); krb5_free_creds(Z_krb5_ctx, credsp); if (retval) { syslog(LOG_ERR, "bdump_get: cannot send authenticator: %s", error_message(retval)); krb5_free_data_contents(Z_krb5_ctx, &data); cleanup(server); return; } krb5_free_data_contents(Z_krb5_ctx, &data); memset((char *)&data, 0, sizeof(krb5_data)); retval = GetKrb5Data(live_socket, &data); if (retval) { syslog(LOG_ERR, "bdump_get: cannot get auth response: %s", error_message(retval)); cleanup(server); return; } retval = krb5_rd_rep(Z_krb5_ctx, bdump_ac, &data, &rep); free(data.data); memset((char *)&data, 0, sizeof(krb5_data)); if (retval) { syslog(LOG_ERR, "bdump_get: mutual authentication failed: %s", error_message(retval)); cleanup(server); return; } break; #endif #ifdef HAVE_KRB4 case 4: bdump_auth_proc = Z_FormatAuthHeaderWithASCIIAddress; /* send an authenticator */ retval = SendKerberosData(live_socket, &ticket, SERVER_SERVICE, SERVER_INSTANCE); if (retval != 0) { syslog(LOG_ERR,"bdump_get: %s", error_message(retval)); cleanup(server); return; } zdbug((LOG_DEBUG, "bdump_get: SendKerberosData ok")); /* get his authenticator */ retval = GetKerberosData(live_socket, from.sin_addr, &kdata, SERVER_SERVICE, srvtab_file); if (retval != KSUCCESS) { syslog(LOG_ERR, "bdump_get getkdata: %s",error_message(retval)); cleanup(server); return; } if (strcmp(kdata.pname, SERVER_SERVICE) || strcmp(kdata.pinst, SERVER_INSTANCE) || strcmp(kdata.prealm, ZGetRealm())) { syslog(LOG_ERR, "bdump_get: peer not zephyr in lrealm: %s.%s@%s", kdata.pname, kdata.pinst,kdata.prealm); cleanup(server); return; } break; #endif /* HAVE_KRB4 */ } #endif /* defined(HAVE_KRB4) || defined(HAVE_KRB5) */ retval = setup_file_pointers(); if (retval != 0) { syslog(LOG_WARNING, "bdump_get: can't set up file pointers: %s", error_message (retval)); cleanup(server); return; } retval = bdump_recv_loop(server); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_get: bdump_recv_loop failed: %s", error_message(retval)); cleanup(server); return; } zdbug((LOG_DEBUG,"bdump_get: gbdl ok")); retval = bdump_send_loop(server); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_send_loop failed: %s", error_message(retval)); cleanup(server); return; } zdbug((LOG_DEBUG, "bdump_get: gbd finished")); /* set this guy to be up, and schedule a hello */ server->state = SERV_UP; timer_reset(server->timer); server->timer = timer_set_rel(0L, server_timo, server); zdbug((LOG_DEBUG,"cleanup gbd")); shutdown_file_pointers(); #ifdef _POSIX_VERSION action.sa_handler = SIG_DFL; sigaction(SIGPIPE, &action, NULL); #else signal(SIGPIPE, SIG_DFL); #endif bdumping = 0; server->dumping = 0; /* Now that we are finished dumping, send all the queued packets */ server_send_queue(server); return; } void bdump_get(ZNotice_t *notice, int auth, struct sockaddr_in *who, Server *server) { void (*proc)(ZNotice_t *, int, struct sockaddr_in *, Server *); proc = NULL; if (zdebug) { syslog(LOG_DEBUG, "bdump_get: bdump v%s avail %s", notice->z_class_inst, inet_ntoa(who->sin_addr)); } if (strcmp (notice->z_class_inst, "1.2") == 0) proc = bdump_get_v12; if (proc) { (*proc)(notice, auth, who, server); } else { syslog(LOG_WARNING, "bdump_get: Incompatible bdump version '%s' from %s", notice->z_class_inst, inet_ntoa(who->sin_addr)); } } /* * Send off a packet via tcp */ static Code_t transmit_tcp(char *pack, int packlen) { Code_t retval = ZERR_NONE; u_short length; int count; #ifdef HAVE_KRB5 krb5_data indata, outmsg; #endif /* output and bdump_ac are globals */ if (packlen > Z_MAXPKTLEN) { syslog(LOG_ERR, "transmit_tcp: packet is too large (%d bytes)", packlen); return ZERR_PKTLEN; } #ifdef HAVE_KRB5 if (bdump_ac) { indata.length = packlen; indata.data = pack; memset(&outmsg, 0, sizeof(krb5_data)); retval = krb5_mk_priv(Z_krb5_ctx, bdump_ac, &indata, &outmsg, NULL); if (retval != ZERR_NONE) goto cleanup; packlen = outmsg.length; pack = outmsg.data; } #endif length = htons((unsigned short) packlen); count = net_write(output, (char *) &length, sizeof(length)); if (count != sizeof(length)) { if (count < 0) { syslog(LOG_WARNING, "transmit_tcp: writing length: %m"); retval = errno; } else { syslog(LOG_WARNING, "transmit_tcp: writing length: %lu vs %d", (unsigned long)sizeof(length), count); retval = ZSRV_LEN; } goto cleanup; } count = net_write(output, pack, packlen); if (count != packlen) { if (count < 0) { syslog(LOG_WARNING, "transmit_tcp: writing data: %m"); retval = errno; } else { syslog(LOG_WARNING, "transmit_tcp: writing data: %d vs %d", packlen, count); retval = ZSRV_LEN; } goto cleanup; } cleanup: #ifdef HAVE_KRB5 if (bdump_ac) krb5_free_data_contents(Z_krb5_ctx, &outmsg); #endif return retval; } /* * Send a list off as the specified notice */ Code_t bdump_send_list_tcp(ZNotice_Kind_t kind, struct sockaddr_in *addr, char *class_name, char *inst, char *opcode, char *sender, char *recip, char **lyst, int num) { ZNotice_t notice; char *pack; int packlen; Code_t retval; memset (¬ice, 0, sizeof(notice)); notice.z_kind = kind; notice.z_port = addr->sin_port; notice.z_class = class_name; notice.z_class_inst = inst; notice.z_opcode = opcode; notice.z_sender = sender; notice.z_recipient = recip; notice.z_default_format = ""; notice.z_num_other_fields = 0; if (addr) notice.z_sender_sockaddr.ip4 = *addr; /*XXX*/ retval = ZFormatNoticeList(¬ice, lyst, num, &pack, &packlen, bdump_auth_proc); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_send_list_tcp: ZFormatNotice: %s", error_message(retval)); return retval; } retval = transmit_tcp(pack, packlen); free(pack); if (retval != ZERR_NONE) syslog(LOG_ERR, "bdump_send_list_tcp: transmit_tcp: %s", error_message(retval)); return retval; } static void shutdown_file_pointers(void) { if (input) { fclose(input); input = 0; } if (output) { fclose(output); output = 0; } if (live_socket >= 0) { close(live_socket); live_socket = -1; #ifdef HAVE_KRB5 if (bdump_ac) krb5_auth_con_free(Z_krb5_ctx, bdump_ac); bdump_ac = NULL; #endif } } static void cleanup(Server *server) { #ifdef _POSIX_VERSION struct sigaction action; #endif zdbug((LOG_DEBUG, "bdump cleanup")); if (server != limbo_server) { if (server->state != SERV_STARTING) server->state = SERV_DEAD; timer_reset(server->timer); server->timer = timer_set_rel(server->timeout, server_timo, server); } shutdown_file_pointers (); #ifdef _POSIX_VERSION action.sa_flags = 0; sigemptyset(&action.sa_mask); action.sa_handler = SIG_DFL; sigaction(SIGPIPE,&action, NULL); #else signal(SIGPIPE, SIG_DFL); #endif /* _POSIX_VERSION */ bdumping = 0; server->dumping = 0; } #if defined(HAVE_KRB4) || defined(HAVE_KRB5) int got_des = 0; #ifndef HAVE_KRB4 unsigned int enctypes[] = {ENCTYPE_DES_CBC_CRC, ENCTYPE_DES_CBC_MD4, ENCTYPE_DES_CBC_MD5, #ifdef ENCTYPE_DES_CBC_RAW ENCTYPE_DES_CBC_RAW, #endif 0}; #endif int get_tgt(void) { int retval = 0; #ifndef HAVE_KRB4 int i; krb5_keytab_entry kt_ent; #endif #ifdef HAVE_KRB4 /* MIT Kerberos 4 get_svc_in_tkt() requires instance to be writable and * at least INST_SZ bytes long. */ static char buf[INST_SZ + 1] = SERVER_INSTANCE; /* have they expired ? */ if (ticket_time < NOW - tkt_lifetime(TKTLIFETIME) + (15L * 60L)) { /* +15 for leeway */ zdbug((LOG_DEBUG,"get new tickets: %d %d %d", ticket_time, NOW, NOW - tkt_lifetime(TKTLIFETIME) + 15L)); dest_tkt(); retval = krb_get_svc_in_tkt(SERVER_SERVICE, buf, (char *)ZGetRealm(), "krbtgt", (char *)ZGetRealm(), TKTLIFETIME, srvtab_file); if (retval != KSUCCESS) { syslog(LOG_ERR,"get_tgt: krb_get_svc_in_tkt: %s", error_message(retval)); ticket_time = 0; return(1); } else { ticket_time = NOW; } retval = read_service_key(SERVER_SERVICE, SERVER_INSTANCE, (char *)ZGetRealm(), 0 /*kvno*/, srvtab_file, (char *)serv_key); if (retval != KSUCCESS) { syslog(LOG_ERR, "get_tgt: read_service_key: %s", error_message(retval)); return 1; } des_key_sched(serv_key, serv_ksched.s); got_des = 1; } #endif #ifdef HAVE_KRB5 /* XXX */ if (ticket5_time < NOW - tkt5_lifetime(TKT5LIFETIME) + (15L * 60L)) { krb5_keytab kt; krb5_get_init_creds_opt opt; krb5_creds cred; krb5_principal principal; memset(&cred, 0, sizeof(cred)); retval = krb5_build_principal(Z_krb5_ctx, &principal, strlen(ZGetRealm()), ZGetRealm(), SERVER_KRB5_SERVICE, SERVER_INSTANCE, NULL); if (retval) { syslog(LOG_ERR, "get_tgt: krb5_build_principal: %s", error_message(retval)); return 1; } krb5_get_init_creds_opt_init (&opt); krb5_get_init_creds_opt_set_tkt_life (&opt, TKT5LIFETIME); retval = krb5_kt_resolve(Z_krb5_ctx, keytab_file, &kt); if (retval) { syslog(LOG_ERR, "get_tgt: krb5_kt_resolve: %s", error_message(retval)); krb5_free_principal(Z_krb5_ctx, principal); return 1; } retval = krb5_get_init_creds_keytab (Z_krb5_ctx, &cred, principal, kt, 0, NULL, &opt); if (retval) { syslog(LOG_ERR, "get_tgt: krb5_get_init_creds_keytab: %s", error_message(retval)); krb5_free_principal(Z_krb5_ctx, principal); krb5_kt_close(Z_krb5_ctx, kt); return 1; } #ifndef HAVE_KRB4 for (i = 0; enctypes[i]; i++) { retval = krb5_kt_get_entry(Z_krb5_ctx, kt, principal, 0, enctypes[i], &kt_ent); if (!retval) break; } if (!retval) { #ifdef HAVE_KRB5_CRYPTO_INIT retval = krb5_copy_keyblock(Z_krb5_ctx, &kt_ent.keyblock, &server_key); #else retval = krb5_copy_keyblock(Z_krb5_ctx, &kt_ent.key, &server_key); #endif if (retval) { syslog(LOG_ERR, "get_tgt: krb5_copy_keyblock: %s", error_message(retval)); krb5_free_principal(Z_krb5_ctx, principal); krb5_kt_close(Z_krb5_ctx, kt); return 1; } got_des = 1; } #endif /* HAVE_KRB4 */ krb5_free_principal(Z_krb5_ctx, principal); krb5_kt_close(Z_krb5_ctx, kt); retval = krb5_cc_initialize (Z_krb5_ctx, Z_krb5_ccache, cred.client); if (retval) { syslog(LOG_ERR, "get_tgt: krb5_cc_initialize: %s", error_message(retval)); return 1; } retval = krb5_cc_store_cred (Z_krb5_ctx, Z_krb5_ccache, &cred); if (retval) { syslog(LOG_ERR, "get_tgt: krb5_cc_store_cred: %s", error_message(retval)); return 1; } ticket5_time = NOW; krb5_free_cred_contents (Z_krb5_ctx, &cred); } #endif return 0; } #endif /* HAVE_KRB4 */ /* * The braindump offer wasn't taken, so we retract it. */ /*ARGSUSED*/ static void close_bdump(void *arg) { if (bdump_socket >= 0) { FD_CLR(bdump_socket, &interesting); close(bdump_socket); nfds = srv_socket + 1; bdump_socket = -1; zdbug((LOG_DEBUG, "bdump not used")); } else { zdbug((LOG_DEBUG, "bdump not open")); } return; } /* * Start receiving instruction notices from the brain dump socket */ static Code_t bdump_recv_loop(Server *server) { ZNotice_t notice; char *packet = NULL; int len; int pblen = Z_MAXPKTLEN; Code_t retval; Client *client = NULL; struct sockaddr_in who; #ifdef HAVE_KRB5 uint32_t client_enctype; uint32_t client_keysize; unsigned char buf[512]; int blen; #endif #if defined(HAVE_KRB4) || defined(HAVE_KRB5) char *cp; #ifndef HAVE_KRB4 unsigned char cblock[8]; #else C_Block cblock; #endif #endif ZRealm *realm = NULL; zdbug((LOG_DEBUG, "bdump_recv_loop")); packet = malloc(Z_MAXPKTLEN); if (packet == NULL) return ENOMEM; /* do the inverse of bdump_send_loop, registering stuff on the fly */ while (1) { if (packets_waiting()) { /* A non-braindump packet is waiting; handle it. */ bdumping = 0; bdump_concurrent = 1; handle_packet(); bdump_concurrent = 0; bdumping = 1; } len = sizeof(packet); retval = get_packet(&packet, &pblen, &len); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_recv_loop: get_packet failed: %s", error_message(retval)); free(packet); return retval; } #if HAVE_KRB5 if (bdump_ac) { krb5_data in, out; in.length = len; in.data = packet; memset(&out, 0, sizeof(krb5_data)); retval = krb5_rd_priv(Z_krb5_ctx, bdump_ac, &in, &out, NULL); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_recv_loop: krb5_rd_priv failed: %s", error_message(retval)); free(packet); return retval; } memcpy(packet, out.data, out.length); len = out.length; krb5_free_data_contents(Z_krb5_ctx, &out); } #endif retval = ZParseNotice(packet, len, ¬ice); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_recv_loop: ZParseNotice failed: %s", error_message(retval)); free(packet); return retval; } #if defined (DEBUG) if (zdebug) { syslog(LOG_DEBUG, "bdump_recv_loop: %s '%s' '%s' '%s' '%s' '%s'", ZNoticeKinds[(int) notice.z_kind], notice.z_class, notice.z_class_inst, notice.z_opcode, notice.z_sender, notice.z_recipient); } #endif /* DEBUG */ who.sin_family = AF_INET; /*XXX*/ who.sin_addr.s_addr = notice.z_sender_sockaddr.ip4.sin_addr.s_addr; who.sin_port = notice.z_port; if (strcmp(notice.z_opcode, ADMIN_DONE) == 0) { /* end of brain dump */ free(packet); return ZERR_NONE; } else if (strcmp(notice.z_opcode, ADMIN_NEWREALM) == 0) { /* get a realm from the message */ realm = realm_get_realm_by_name(notice.z_message); if (!realm) { syslog(LOG_ERR, "bdump_recv_loop: realm_get_realm_by_name failed: no realm %s", notice.z_message); } } else if (strcmp(notice.z_class, LOGIN_CLASS) == 0) { /* 1 = tell it we are authentic */ retval = ulogin_dispatch(¬ice, 1, &who, server); if (retval != ZERR_NONE) { syslog(LOG_ERR, "bdump_recv_loop: ulogin_dispatch failed: %s", error_message(retval)); free(packet); return retval; } } else if (strcmp(notice.z_opcode, ADMIN_NEWCLT) == 0) { /* a new client */ notice.z_port = htons((u_short) atoi(notice.z_message)); retval = client_register(¬ice, &who.sin_addr, &client, 0); if (retval != ZERR_NONE) { syslog(LOG_ERR,"bdump_recv_loop: client_register failed: %s", error_message(retval)); free(packet); return retval; } #ifdef HAVE_KRB5 client->session_keyblock = NULL; if (*notice.z_class_inst) { /* check out this session key I found */ cp = notice.z_message + strlen(notice.z_message) + 1; if (*cp == '0' && got_des) { /* ****ing netascii; this is an encrypted DES keyblock XXX this code should be conditionalized for server transitions */ retval = Z_krb5_init_keyblock(Z_krb5_ctx, ENCTYPE_DES_CBC_CRC, sizeof(cblock), &client->session_keyblock); if (retval) { syslog(LOG_ERR, "bdump_recv_loop: failed to allocate DES keyblock: %s", error_message(retval)); free(packet); return retval; } retval = ZReadAscii(cp, strlen(cp), cblock, sizeof(cblock)); if (retval != ZERR_NONE) { syslog(LOG_ERR,"bdump_recv_loop: bad cblock read: %s (%s)", error_message(retval), cp); } else { retval = des_service_decrypt(cblock, Z_keydata(client->session_keyblock)); if (retval) { syslog(LOG_ERR, "bdump_recv_loop: failed to decyrpt DES session key: %s", error_message(retval)); free(packet); return retval; } } } else if (*cp == 'Z') { /* Zcode! Long live the new flesh! */ retval = ZReadZcode((unsigned char *)cp, buf, sizeof(buf), &blen); if (retval != ZERR_NONE) { syslog(LOG_ERR,"bdump_recv_loop: bad keyblock read: %s (%s)", error_message(retval), cp); } else { memcpy(&client_enctype, &buf[0], sizeof(uint32_t)); memcpy(&client_keysize, &buf[4], sizeof(uint32_t)); retval = Z_krb5_init_keyblock(Z_krb5_ctx, ntohl(client_enctype), ntohl(client_keysize), &client->session_keyblock); if (retval) { syslog(LOG_ERR, "bdump_recv_loop: failed to allocate keyblock: %s", error_message(retval)); free(packet); return retval; } memcpy(Z_keydata(client->session_keyblock), &buf[8], Z_keylen(client->session_keyblock)); } } } #else #ifdef HAVE_KRB4 memset(client->session_key, 0, sizeof(C_Block)); if (*notice.z_class_inst) { /* a C_Block is there */ cp = notice.z_message + strlen(notice.z_message) + 1; retval = ZReadAscii(cp, strlen(cp), cblock, sizeof(C_Block)); if (retval != ZERR_NONE) { syslog(LOG_ERR,"bdump_recv_loop: bad cblock read: %s (%s)", error_message(retval), cp); } else { des_ecb_encrypt((des_cblock *)cblock, (des_cblock *)client->session_key, serv_ksched.s, DES_DECRYPT); } } #endif /* HAVE_KRB4 */ #endif } else if (strcmp(notice.z_opcode, CLIENT_SUBSCRIBE) == 0) { /* a subscription packet */ if (!client) { syslog(LOG_ERR, "bdump_recv_loop: no client"); free(packet); return ZSRV_NOCLT; } retval = subscr_subscribe(client, ¬ice, server); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_recv_loop: subscr_subscribe failed: %s", error_message(retval)); free(packet); return retval; } } else if (strcmp(notice.z_opcode, REALM_SUBSCRIBE) == 0) { /* add a subscription for a realm */ if (realm) { retval = subscr_realm(realm, ¬ice); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "bdump_recv_loop: subscr_realm failed: %s", error_message(retval)); free(packet); return retval; } } /* else */ /* Other side tried to send us subs for a realm we didn't know about, and so we drop them silently */ } else { syslog(LOG_ERR, "bdump_recv_loop: bad opcode %s",notice.z_opcode); free(packet); return ZSRV_UNKNOWNOPCODE; } } } /* * Send all the state to the peer. */ static Code_t bdump_send_loop(Server *server) { Code_t retval; zdbug((LOG_DEBUG, "bdump send loop")); retval = uloc_send_locations(); if (retval != ZERR_NONE) return retval; retval = client_send_clients(); if (retval != ZERR_NONE) return retval; retval = realm_send_realms(); if (retval != ZERR_NONE) return retval; return send_done(); } /* * Send a sync indicating end of this host */ static Code_t send_done(void) { Code_t retval; zdbug((LOG_DEBUG, "send_done")); retval = send_normal_tcp(SERVACK, bdump_sin.sin_port, ZEPHYR_ADMIN_CLASS, "", ADMIN_DONE, myname, "", NULL, 0); return retval; } /* * Send a list off as the specified notice */ static Code_t send_list(ZNotice_Kind_t kind, int port, char *class_name, char *inst, char *opcode, char *sender, char *recip, char **lyst, int num) { ZNotice_t notice; char *pack; int packlen; Code_t retval; memset (¬ice, 0, sizeof(notice)); notice.z_kind = kind; notice.z_port = port; notice.z_class = class_name; notice.z_class_inst = inst; notice.z_opcode = opcode; notice.z_sender = sender; notice.z_recipient = recip; notice.z_default_format = ""; notice.z_num_other_fields = 0; retval = ZFormatNoticeList(¬ice, lyst, num, &pack, &packlen, ZNOAUTH); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "sl format: %s", error_message(retval)); return retval; } retval = ZSendPacket(pack, packlen, 0); if (retval != ZERR_NONE) syslog(LOG_WARNING, "sl xmit: %s", error_message(retval)); free(pack); return retval; } /* * Send a message off as the specified notice, via TCP */ static Code_t send_normal_tcp(ZNotice_Kind_t kind, int port, char *class_name, char *inst, char *opcode, char *sender, char *recip, char *message, int len) { ZNotice_t notice; char *pack; int packlen; Code_t retval; memset (¬ice, 0, sizeof(notice)); notice.z_kind = kind; notice.z_port = port; notice.z_class = class_name; notice.z_class_inst = inst; notice.z_opcode = opcode; notice.z_sender = sender; notice.z_recipient = recip; notice.z_default_format = ""; notice.z_message = message; notice.z_message_len = len; notice.z_num_other_fields = 0; retval = ZFormatNotice(¬ice, &pack, &packlen, bdump_auth_proc); if (retval != ZERR_NONE) { syslog(LOG_WARNING, "send_normal_tcp: ZFormatNotice: %s", error_message(retval)); return retval; } retval = transmit_tcp(pack, packlen); free(pack); if (retval != ZERR_NONE) syslog(LOG_ERR, "send_normal_tcp: transmit_tcp: %s", error_message(retval)); return retval; } /* * get a packet from the TCP socket * return 0 if successful, error code else */ static Code_t get_packet(char **packet, int *buflen, int *retlen) { unsigned short length; int result; char *p; result = net_read(input, (char *) &length, sizeof(unsigned short)); if (result < (int)sizeof(short)) { if (result < 0) { return errno; } else { syslog(LOG_ERR, "get_packet: received length: %d vs %lu (%m)", result, (unsigned long)sizeof(short)); return ZSRV_LEN; } } length = ntohs(length); if (*buflen < length) { p = realloc(*packet, length); if (p == NULL) { syslog(LOG_ERR, "get_packet: failed to expand buffer to %d bytes from %d", *buflen, length); return ZSRV_BUFSHORT; } syslog(LOG_DEBUG, "get_packet: expanded buffer from %d bytes to %d", *buflen, length); *buflen = length; *packet = p; } result = net_read(input, *packet, (int) length); if (result < length) { if (result < 0) { return errno; } else { syslog(LOG_ERR, "get_pkt: %d vs %d (%m)", result, length); return ZSRV_LEN; } } *retlen = length; return ZERR_NONE; } static Code_t extract_sin(ZNotice_t *notice, struct sockaddr_in *target) { char *cp = notice->z_message; char *buf; buf = cp; if (!notice->z_message_len || *buf == '\0') { return ZSRV_PKSHORT; } target->sin_addr.s_addr = inet_addr(cp); cp += (strlen(cp) + 1); /* past the null */ if ((cp >= notice->z_message + notice->z_message_len) || (*cp == '\0')) { return(ZSRV_PKSHORT); } target->sin_port = htons((unsigned short)atoi(cp)); target->sin_family = AF_INET; return ZERR_NONE; } static int net_read(FILE *f, char *buf, int len) { int cc, len2 = 0; fflush (output); do { errno = 0; cc = fread(buf, 1, len, f); if (cc == 0) { if (feof(f)) return len2; if (errno == 0) errno = EIO; return -1; } buf += cc; len2 += cc; len -= cc; } while (len > 0); return len2; } static int net_write(FILE *f, char *buf, int len) { int cc; int wrlen = len; do { cc = fwrite (buf, 1, wrlen, f); if (cc == 0) return -1; buf += cc; wrlen -= cc; } while (wrlen > 0); return len; } static int setup_file_pointers (void) { int fd; input = fdopen (live_socket, "r"); if (!input) return errno; fd = dup (live_socket); if (fd < 0) return errno; output = fdopen (fd, "w"); if (!output) return errno; return 0; } #ifdef HAVE_KRB5 static int des_service_decrypt(unsigned char *in, unsigned char *out) { #ifndef HAVE_KRB4 krb5_data dout; #ifdef HAVE_KRB5_C_DECRYPT krb5_enc_data din; dout.length = 8; dout.data = (char *)out; /*What*/ din.ciphertext.length = 8; din.ciphertext.data = (char *)in; din.enctype = Z_enctype(server_key); #ifdef HAVE_KRB5_CRYPTO_INIT return krb5_c_decrypt(Z_krb5_ctx, *server_key, 0, 0, &din, &dout); #else return krb5_c_decrypt(Z_krb5_ctx, server_key, 0, 0, &din, &dout); #endif #elif defined(HAVE_KRB5_CRYPTO_INIT) int ret; krb5_crypto crypto; dout.length = 8; dout.data = out; ret = krb5_crypto_init(Z_krb5_ctx, server_key, Z_enctype(server_key), &crypto); if (ret) return ret; ret = krb5_decrypt_ivec(Z_krb5_ctx, crypto, 0, in, 8, &dout, NULL); krb5_crypto_destroy(Z_krb5_ctx, crypto); return ret; #endif #else des_ecb_encrypt((C_Block *)in, (C_Block *)out, serv_ksched.s, DES_DECRYPT); return 0; /* sigh */ #endif } #endif