/*
    Tucnak - VHF contest log
    Copyright (C) 2002-2022  Ladislav Vaiz <ok1zia@nagano.cz>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

*/


#include "header.h"

#include "bfu.h"
#include "control.h"
#include "cwdb.h"
#include "dxc.h"
#include "edi.h"
#include "fifo.h"
#include "main.h"
#include "map.h"
#include "menu.h"
#include "namedb.h"
#include "net.h"
#include "rc.h"
#include "rotar.h"
#include "session.h"
#include "sked.h"
#include "ssbd.h"
#include "stats.h"
#include "subwin.h"
#include "tsdl.h"

#define CLEAN_TRACE

/*
"relation" layer:
    KA seq          keepalive
    AK seq          ack

    AC tdate;pcall;tname;qrvbands   active contest
    CL              close contest

    LA source;stamp
    QS band;date_str;time_str;callsign_or_error; mode;rsts;qsonrs;rstr;\
       qsonrr;exc;locator;source; operator;stamp;callsign;; \
       ser_id;remark;qrg;phase
    TA talk
	TB talk after connect
    SK now_time;operator;src_shortpband;qrg;
       we_call;pband;time_str;callsign;
       locator;remark
    ID 192.168.1.1:55555
    SR start of replication block
    ER end of replication block
    GR band;ccmd;slaveid                            grab band
    DO                                              discovery peers with operator
    PO peer0ip:peer0port;oper0;peer1ip:peer1port;oper1;...       peers list with operator(master->slave)
    DR                                              discovery read-write bands
    PR peer0ip:peer0port;oper0;bands0;peer1ip:peer1port;oper1;bands1;...
                                                    read-write bands (master->slave)
    DC                                              discovery peers with opened contest
    PC peer0ip:peer0port;date0;pcall0;tname0;peer1ip:peer1port;...       peers list with contest properties(master->slave)
    RW bandchars                                    send read-write bands
    PT band;srcip:srcport;tx                        ptt state
    WI band;srcip:srcport;text                      current inputline
    WT band;srcip:srcport;item_no;text              current tmpqso item
    RT band;destip:dstport                          request all tmpqso informations (PT, WI, WT)

    DI                                              OBSOLETE discovery peers
    PE peer0ip:peer0port;peer1ip:peer1port;...      OBSOLETE peers list (master->slave)
    CR destip:destport;srcip:srcport                configuration request
    CF destip:destport;srcip:srcport;config_line    one configuration line

    WR destip:destport;srcip:srcport                        C_W request
    CW destip:destport;srcip:srcport;call;locator;date      one C_W line

    DX DX de HL2IFR:    24900.2  BA4ED        AS-136                         0807Z
    OP operator

    BA pband;psect;stxeq;spowe;sreq;sante;santh;mope1;mope2;remarks

    RS ip:port;rotstr;qtf?;ele?
	RU ip:port;rotstr;qtf;ele
	RP ip:port;rotstr

    RC destip:destport;srcip:srcport                request of contest options
    SC destip:destport;contest_opts;band_number;band0_opts;band1_opts;...
                                                    current contest options
    AR salt                                         authentication request
    AU user;md5(user;pass)                          authenticate slave to remote instance
    BC message                                      bad credentials
	D3                                              discovery peers v3 (flooding to all others)
	P3 peerip:peerport;operator;rwbands;cdate;pcall;tname
													peer info v3 (send by peer about itself)
    R3 destip:destport;srcip:srcport;load_opts		request for contest options v3 (flood)
	C3 destip:destport;contest_opts;band_number;band0_opts;band1_opts;...
	                                                contest options v3 (flood)
	SD destip:destport								start of long data transfer
    QR destip:destport;item0;item1;...itemN         QRV database
	CV destip:destport;item0;item1;...itemN         C_W database
	NA destip:destport;item0;item1;...itemN         name database
	ET destip:destport								end of long data transfer
	EB destip:destport								end of long data transfer back

    VS destip:destport;voipip:voipport              voip spy
    EV destip:destport;voipip:voipport              end of voip spy
    VA destip:destport;voipip:voipport              voip addr spied->spying

*/


struct net *gnet;
char *ns_desc[]={"INIT", "SABM", "CONN", "WAIT", "DIST", "DEAD", "LONG"};

struct net *init_net(void){
     struct net *net;

	progress(VTEXT(T_INIT_NET));

/*    dbg("init_net()\n");*/
    gnet = net = g_new0(struct net, 1);
    net->master_priority = cfg->net_masterpriority;

    init_net_tcp(net);
    init_net_udp(net); /* require gnet->my.sin_port */
    net_connect_remote(net);
    return net;
}

void free_net(struct net *net){

	progress(VTEXT(T_FREE_NET));
    free_net_ifaces(net);
    free_net_tcp(net);
    free_net_udp(net);
	if (gnet->peers3) g_string_free(gnet->peers3, TRUE);
    g_free(net);
}

#define NET_DELIM " \t,"



int init_net_udp(struct net *net){
    struct sockaddr_in sin;
    socklen_t socklen;

    init_net_ifaces(net, 1);

/*    dbg("init_net_udp()\n");*/
    gnet->udptimer_id = -1;
    gnet->udpsock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (gnet->udpsock < 0) goto err;
/*    dbg("   udpsock=%d\n", gnet->udpsock);*/

    if (z_sock_reuse(gnet->udpsock, 1)){
        trace(cfg->trace_bcast, "Can't set SO_REUSEADDR\n");
        goto err;
    }

	if (z_sock_broadcast(gnet->udpsock, 1)){
        trace(cfg->trace_bcast, "Can't set SO_BROADCAST\n");
        goto err;
    }

    if (z_sock_nonblock(gnet->udpsock, 1)){
        trace(cfg->trace_bcast, "Can't set O_NONBLOCK\n");
        goto err;
    }

    memset(&sin, 0, sizeof(struct sockaddr_in));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(NET_PORT);
    sin.sin_addr.s_addr = INADDR_ANY;
    if (bind(gnet->udpsock, (struct sockaddr *)&sin, sizeof(sin))){
        trace(cfg->trace_bcast, "Can't bind UDP port %d\n", NET_PORT);
        goto err;
    }

    memset(&sin, 0, sizeof(sin));
    socklen = sizeof(sin);
    getsockname(gnet->udpsock, (struct sockaddr *) &sin, &socklen);

    gnet->udptimer_period = UDP_ANNOUNCE;
    gnet->udptimer_id = zselect_timer_new(zsel, 1, udp_timer, net);
    zselect_set(zsel, gnet->udpsock, udp_read_handler, NULL, udp_exception_handler, NULL);

/*    dbg("I'M %s:%d\n", inet_ntoa(gnet->my.sin_addr),
 *    ntohs(gnet->my.sin_port));*/
//    trace(cfg->trace_bcast, "My ID: %s\n", gnet->myid);
    return 0;
err:;
    closesocket(gnet->udpsock);
    gnet->udpsock = -1;

    /* zselect_timer_kill not needed */
    return 1;
}


int init_net_tcp(struct net *net){
    int port;
    struct sockaddr_in sin;

    /*dbg("init_net_tcp()\n");*/

    gnet->peers = g_ptr_array_new();

    gnet->tcpsock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    /*dbg("   tcpsock=%d\n", gnet->tcpsock);*/

/*#ifndef WIN32
	//__CYGWIN__
    {
        int on;
        on=1;
        if (setsockopt(gnet->tcpsock, SOL_SOCKET, SO_REUSEADDR, SO4 &on, sizeof(on))){
            trace(cfg->trace_sock, "Can't set SO_REUSEADDR\n");
            goto x;
        }
    }
#endif    */
#ifdef Z_UNIX   // winsock with SO_REUSEADDR allow more programs to bind one port
    if (z_sock_reuse(gnet->tcpsock, 1)){
        trace(cfg->trace_sock, "Can't set SO_REUSEADRR\n");
        goto x;
    }
#endif

    if (z_sock_nonblock(gnet->tcpsock, 1)){
        trace(cfg->trace_sock, "Can't set O_NONBLOCK\n");
        goto x;
    }

    for (port=NET_PORT; port<65536; port++){
        gnet->my.sin_port = htons(port);
        gnet->global.sin_port = htons(port);
		gnet->global_priority = cfg->net_masterpriority;
        memset(&sin, 0, sizeof(struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_port = gnet->my.sin_port;
        sin.sin_addr.s_addr = INADDR_ANY;
        if (bind(gnet->tcpsock, (struct sockaddr *)&sin, sizeof(sin))==0) break;

/*        dbg("Can't bind tcp port %d\n", NET_PORT);*/
        if (port==65535) goto x;
        continue;
    }

    if (listen(gnet->tcpsock, 10)){
        trace(cfg->trace_sock, "Can't listen on socket %d, tcp port %d \n", gnet->tcpsock, gnet->my.sin_port);
        goto x;
    }

    sock_debug(gnet->tcpsock, "listen init_net_tcp");
    zselect_set(zsel, gnet->tcpsock, tcp_accept_handler, NULL, tcp_exception_handler, NULL);

    return 0;
x:;
    sock_debug(gnet->tcpsock, "close gnet->tcpsock");
    closesocket(gnet->tcpsock);
    gnet->my.sin_port=0;
    gnet->global.sin_port=0;
    gnet->tcpsock=-1;
    return -1;
}

enum assign_types {AT_UNASSIGNED, AT_LOOPBACK, AT_ASSIGNED};

int init_net_ifaces(struct net *net, int free_global){
    struct ziface_struct ifaces[MAX_INTERFACES];
    int i,mi,j,ii;
    GPtrArray *if_ignore, *ip_ignore, *ip_announce;
    char *token_ptr;
    gchar *c;
    GString *gs;
    enum assign_types assign_type;
	char *tmp1;


    if_ignore = g_ptr_array_new();
    ip_ignore = g_ptr_array_new();
    ip_announce = g_ptr_array_new();

    if (cfg->net_if_ignore){
        gchar *net_if_ignore;
        net_if_ignore = g_strdup(cfg->net_if_ignore);
        for (c=strtok_r(net_if_ignore, NET_DELIM, &token_ptr); c!=NULL;
                c=strtok_r(NULL, NET_DELIM, &token_ptr)){

            trace(cfg->trace_bcast, "adding iface '%s' for ignoring", c);
            c=g_strdup(c);
/*            dbg("dup=%p\n", c);*/
            g_ptr_array_add(if_ignore, c);
        }
        g_free(net_if_ignore);
    }

    if (cfg->net_ip_ignore){
        gchar *net_ip_ignore;
        net_ip_ignore = g_strdup(cfg->net_ip_ignore);
        for (c=strtok_r(cfg->net_ip_ignore, NET_DELIM, &token_ptr); c!=NULL;
                c=strtok_r(NULL, NET_DELIM, &token_ptr)){

            trace(cfg->trace_bcast, "adding addr '%s' for ignoring\n", c);
            g_ptr_array_add(ip_ignore, g_strdup(c));
        }
        g_free(net_ip_ignore);
    }


    /* when tucnak crashes here, remove --enable-leak-debug from configure */
//    dbg("gnet->my %s:%d\n", inet_ntoa(gnet->my.sin_addr), ntohs(gnet->my.sin_port));
    tmp1 = inet_ntoa(gnet->my.sin_addr);
    gnet->myid = g_strdup_printf("%s:%d",tmp1, (int)ntohs(gnet->my.sin_port));
    assign_type = AT_UNASSIGNED;
    trace(cfg->trace_bcast, "preassigned gnet->myid=%s", gnet->myid);

    mi = zifaces_get(ifaces, MAX_INTERFACES, 1); /* network byteorder */
    j = 0;
    for (i=0;i<mi; i++){
        struct sockaddr_in *sin;
        int is_lo;

        trace(cfg->trace_sock, "iface %s\n",ifaces[i].name);
        is_lo=!strcmp(ifaces[i].name, "lo");

/*        if (mi>1 && strcmp(ifaces[i].name, "lo")==0) continue;*/
        if ((int)ifaces[i].ip.s_addr==0) continue;  /* 0.0.0.0 happens on cygwin */

        for (ii=0; ii<if_ignore->len; ii++){
            gchar *c;
            c = (gchar *)g_ptr_array_index(if_ignore, ii);
            if (strcmp(c, ifaces[i].name)==0) {
                trace(cfg->trace_bcast, VTEXT(T_IGN_IFACE_S),ifaces[i].name);
                goto next_loop;
            }
        }

        sin = (struct sockaddr_in *)&gnet->bcast_addr[j];

        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = ifaces[i].ip.s_addr | ~(ifaces[i].netmask.s_addr);
        sin->sin_port = htons(NET_PORT);


        for (ii=0; ii<ip_ignore->len; ii++){
            gchar *c;
            struct in_addr iad;

            c = (gchar *)g_ptr_array_index(ip_ignore, ii);
            if (!inet_aton(c, &iad)) continue; /* invalid addr */
            if (iad.s_addr==sin->sin_addr.s_addr) {
                trace(cfg->trace_bcast, VTEXT(T_IGN_ADDR_S), inet_ntoa(sin->sin_addr));
                goto next_loop;
            }
        }


        trace(cfg->trace_bcast, "adding iface '%s'\n", ifaces[i].name);

        if (assign_type!=AT_ASSIGNED) {
            gnet->my.sin_addr.s_addr = ifaces[i].ip.s_addr;
			if (free_global) {
				gnet->global.sin_addr.s_addr = ifaces[i].ip.s_addr;
				gnet->global_priority = cfg->net_masterpriority;
			}
            if (gnet->myid) g_free(gnet->myid);
            gnet->myid = g_strdup_printf("%s:%d", inet_ntoa(gnet->my.sin_addr), ntohs(gnet->my.sin_port));
            assign_type=is_lo?AT_LOOPBACK:AT_ASSIGNED;
            trace(cfg->trace_bcast, "assigned gnet->myid=%s\n", gnet->myid);
        }
        j++;

        trace(cfg->trace_bcast, VTEXT(T_ADD_IFACE_SSD), ifaces[i].name, inet_ntoa(sin->sin_addr), NET_PORT);
next_loop:;
    }

    if (cfg->net_ip_announce){
        for (c=strtok_r(cfg->net_ip_announce, NET_DELIM, &token_ptr); c!=NULL;
             c=strtok_r(NULL, NET_DELIM, &token_ptr)){

            struct in_addr iad;
            struct sockaddr_in *sin;

            if (!inet_aton(c, &iad)) continue; /* invald addr */

            sin = (struct sockaddr_in *)&gnet->bcast_addr[j];

            sin->sin_family = AF_INET;
            sin->sin_addr.s_addr = iad.s_addr;
            sin->sin_port = htons(NET_PORT);
            log_addf(VTEXT(T_FORCED_ADDR), inet_ntoa(sin->sin_addr));
            j++;
        }
    }


    gnet->max_addrs = j;

    zg_ptr_array_free_all(if_ignore);
    zg_ptr_array_free_all(ip_ignore);
    zg_ptr_array_free_all(ip_announce);

    gs=g_string_sized_new(100);
    for (i=0; i<gnet->max_addrs; i++){
        struct sockaddr_in *sin;

        if (i>0) g_string_append_printf(gs, ", ");
        sin = (struct sockaddr_in *)&gnet->bcast_addr[i];
        g_string_append_printf(gs, "%s:%d", inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
    }
    trace(cfg->trace_bcast, "Broadcast to: %s", gs->str);
    g_string_free(gs, TRUE);
    return 0;
}

void net_connect_remote(struct net *net){
    //dbg("net_connect_remote()\n");

	if (gnet->remote){
		free_conn(gnet->remote);
		zg_free0(gnet->remote);
	}

	if (!cfg->net_remote_enable) return;
	if (!cfg->net_remote_host || !*cfg->net_remote_host) return;
	if (cfg->net_remote_port <= 0 || cfg->net_remote_port > 65535) return;
    if (!strstr(gnet->myid, ":55555")) {
        log_addf(VTEXT(T_ONLY_55555_CAN_REMOTE));
        return;
    }

	if (!gnet->radns) gnet->radns = zasyncdns_init();
	zasyncdns_getaddrinfo(gnet->radns, zsel, net_remote_addrinfo, cfg->net_remote_host, AF_INET, NULL);
}

void net_remote_addrinfo(struct zasyncdns *adns, int n, int *family, int *socktype, int *protocol, int *addrlen, union zsockaddr *addr, char *errorstr){
	int i, ni, j, ip0;
	struct ziface_struct ifaces[MAX_INTERFACES];

	gnet->remote = g_new0(struct conn, 1);
	gnet->remote->sock = -1;
	gnet->remote->start = time(NULL);
    dbg("gnet->remote = %p\n", gnet->remote);

	for (i = 0; i < n; i++){
		if (family[i] != AF_INET) continue; // change: look at address comparsion bellow
		gnet->remote->sin.sin_family = AF_INET;
		memcpy(&gnet->remote->sin, &(addr[i].in), sizeof(struct sockaddr_in));
		gnet->remote->sin.sin_port = ntohs(cfg->net_remote_port);
		conn_set_fid(gnet->remote);

		// check for my address
		ip0 = ntohl(addr[i].in.sin_addr.s_addr) >> 24;
		if (ip0 == 127){
			log_addf(VTEXT(T_CANT_CONNECT_TO_MYSELF_LO));
			return;
		}

		ni = zifaces_get(ifaces, MAX_INTERFACES, 1);
		for (j = 0; j < ni; j++){
			if (ifaces[j].ip.s_addr == addr[i].in.sin_addr.s_addr){   // must be AF_INET!
				log_addf(VTEXT(T_CANT_CONNECT_TO_MYSELF), ifaces[j].name);
				return;
			}
		}

		tcp_connect(gnet->remote);
		return;
	}
	log_addf(VTEXT(T_CANT_PARSE_REMOTE_HOST_S), cfg->net_remote_host);
    tcp_set_state(gnet->remote, NS_DISCONNECTED);
}


void free_net_udp(struct net *net){
    if (gnet->udptimer_id >= 0) zselect_timer_kill(zsel, gnet->udptimer_id);
    closesocket(gnet->udpsock);

}

void free_conn(struct conn *conn){
    int is_remote = conn == gnet->remote;

    dbg("free_conn(%p) %s gnet->remote=%p\n", conn, is_remote?"is_remote":"", gnet->remote);
    if (conn->sock >= 0){
        zselect_set(zsel, conn->sock, NULL, NULL, NULL, NULL);
        sock_debug(conn->sock, "close free_conn()");
        closesocket(conn->sock);
        tcp_kill(conn);
    }
    tcp_set_state(conn, NS_DEAD);
    if (conn->wrbuf) g_string_free(conn->wrbuf, 1);
    if (conn->rdbuf) g_string_free(conn->rdbuf, 1);
    CONDGFREE(conn->operator_);
    CONDGFREE(conn->rwbands);
    CONDGFREE(conn->remote_id);
    CONDGFREE(conn->remote_ac);
	CONDGFREE(conn->fid);
}

void free_net_ifaces(struct net *net){
    if (gnet->myid) g_free(gnet->myid);
}


void free_net_tcp(struct net *net){
    struct conn *conn;
    int i;

    sock_debug(gnet->tcpsock, "close gnet->tcpsock (2)");
    closesocket(gnet->tcpsock);

    if (gnet->master) {
        free_conn(gnet->master);
        g_free(gnet->master);
        gnet->master = NULL;
    }
	if (gnet->remote){
		free_conn(gnet->remote);
		zg_free0(gnet->remote);
	}
	if (gnet->radns) zasyncdns_free(gnet->radns);

    for (i=gnet->peers->len-1; i>=0; i--){
        conn = (struct conn *) g_ptr_array_index(gnet->peers, i);

        free_conn(conn);
        g_ptr_array_remove_index(gnet->peers, i);
        g_free(conn);

    }
    g_ptr_array_free(gnet->peers, TRUE);
    CONDGFREE(gnet->allpeers);
    CONDGFREE(gnet->rwbpeers);
    CONDGFREE(gnet->bpeers);
}


/****************** SELECT's HANDLERS ********************************/

void udp_read_handler(void *arg){
    char s[1024];
    struct sockaddr_in sin;
    socklen_t socklen;
    gchar **items;
    int i;
    time_t expire, now;
    struct sockaddr_in peer;
    struct sockaddr_in master;
    int direct, masterpriority = 0;

/*    dbg("udp_read_handler()\n");*/

    now = time(NULL);

    memset(s, 0, sizeof(s));
    socklen = sizeof(sin);
    recvfrom(gnet->udpsock, s, sizeof(s)-1, 0,
            (struct sockaddr *)&sin, &socklen);

    if (strlen(s)>0 && s[strlen(s)-1]=='\n') s[strlen(s)-1]='\0';
    if (strlen(s)>0 && s[strlen(s)-1]=='\r') s[strlen(s)-1]='\0';

    items=g_strsplit(s, ";", 0);
    for (i=0;i<7;i++) {
        if (items[i]==NULL) {
            g_strfreev(items);
            return;
        }
    }

    if (strcmp(items[0], "tucnak")!=0) {
        g_strfreev(items);
        return;
    }

    peer.sin_addr.s_addr   = inet_addr(items[2]);
    peer.sin_port          = htons(atoi(items[3]));
    master.sin_addr.s_addr = inet_addr(items[4]);
    master.sin_port        = htons(atoi(items[5]));
    expire                 = atoi(items[6]);

//    dbg("%s items[7]=%s  opt_m=%d  master=%p  cmp=%d\n", items[1], items[7], opt_m, gnet->master, cmp_sin(&master, &gnet->my));
    if (items[7] && opt_m && !gnet->master && cmp_sin(&master, &gnet->my)<0){
        struct timeval tv;
        tv.tv_sec = atoi(items[7]);
        tv.tv_usec = 500000; // better than 0
        if (settimeofday(&tv, NULL) == 0){
            log_addf(VTEXT(T_TIME_WAS_SET));
            now = time(NULL);
        }else{
            GString *gs = g_string_new("");
            g_string_append(gs, VTEXT(T_FAILED_TO_SET_TIME));
            g_string_append(gs, " - ");
            z_strerror(gs, errno);
            log_adds(gs->str);
            g_string_free(gs, TRUE);
        }
    }
    direct = 0;
    if (items[7] != NULL && items[8] != NULL)
	{
		if (atoi(items[8]) != 0) direct = 1;
		if (items[9] != NULL) masterpriority = atoi(items[9]);
    }

    g_strfreev(items);

    if (expire < now) return; /* ignore expired information */
    if (expire < now-NET_MAX_SKEW_NEG ||
        expire > now+NET_MAX_SKEW_POS) {
        log_addf(VTEXT(T_TIMESKEW), (int)(now-expire), inet_ntoa(master.sin_addr), ntohs(master.sin_port));
    }
    if (expire > now+NET_MAX_SKEW_POS){
        trace(cfg->trace_bcast, "ignoring expire in future, ignoring\n");
        return;
    }

    /* network byteorder */
    if (gnet->my.sin_addr.s_addr == peer.sin_addr.s_addr &&
        gnet->my.sin_port        == peer.sin_port) return;

    trace(cfg->trace_bcast, "rcvd: %s: %s\n", inet_ntoa(sin.sin_addr),s);

    if (masterpriority == 0 && gnet->v3compatibility == 0){
        gnet->v3compatibility = 1;
        dbg("set v3compatibility=1\n");
        // must reset master
        gnet->global.sin_family      = AF_INET;
        gnet->global.sin_addr.s_addr = gnet->my.sin_addr.s_addr;
        gnet->global.sin_port        = gnet->my.sin_port;
        gnet->global_expire          = now + NET_GLOBAL_EXPIRE;
		gnet->global_priority        = cfg->net_masterpriority;
    }

    //log_addf("udp_read_handler:  direct=%d  my-global = %d", direct, cmp_sin(&gnet->my, &gnet->global));
    if (direct && cmp_sin(&gnet->my, &gnet->global) == 0){
        udp_sendto(&sin, 0);
    }


    if (cmp_sin(&master, &gnet->global)==0){ /* updating expire time */
        gnet->global_expire          = expire;
    }

	if (cmp_master(masterpriority, gnet->global_priority, &master, &gnet->global) < 0){ /* received master is "global" */
        gnet->global.sin_family      = AF_INET;
        gnet->global.sin_addr.s_addr = master.sin_addr.s_addr;
        gnet->global.sin_port        = master.sin_port;
        gnet->global_expire          = expire;
		gnet->global_priority = masterpriority;
        trace(cfg->trace_bcast, "new master is %s:%d, now=%d, expires %d \n", inet_ntoa(gnet->global.sin_addr), ntohs(gnet->global.sin_port), (int)(now%1000), (int)(expire%1000));
    }
    if (cmp_master(masterpriority, gnet->global_priority, &master, &gnet->global) > 0){ /* received master is my slave */
        trace(cfg->trace_bcast, "tucnak %s:%d means it is master but isn't true\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
        zselect_timer_kill(zsel, gnet->udptimer_id);
        gnet->udptimer_id = zselect_timer_new(zsel, 1, udp_timer, gnet);
    }

    /* is gnet->master "global" master? */
    if (gnet->master) {
        if (cmp_sin(&gnet->master->sin, &gnet->global)!=0) { /* gnet->master > gnet->global, disconnect */
            trace(cfg->trace_sock, "disconnecting from %s:%d\n", inet_ntoa(gnet->master->sin.sin_addr), ntohs(gnet->master->sin.sin_port));
            log_addf(VTEXT(T_DISCONNECTING_SD), inet_ntoa(gnet->master->sin.sin_addr), ntohs(gnet->master->sin.sin_port));

            free_conn(gnet->master);
            g_free(gnet->master);
            gnet->master = NULL;
        }
    }

	if (!gnet->master && cmp_master(cfg->net_masterpriority, gnet->global_priority, &gnet->my ,&gnet->global) > 0){
        trace(cfg->trace_sock, "Now i'm a master\n");
        gnet->master = g_new0(struct conn, 1);

        gnet->master->sin.sin_family      = AF_INET;
        gnet->master->sin.sin_addr.s_addr = gnet->global.sin_addr.s_addr;
        gnet->master->sin.sin_port        = gnet->global.sin_port;
		conn_set_fid(gnet->master);

        tcp_connect(gnet->master);
    }

}

void udp_exception_handler(void *arg){

    dbg("udp_exception_handler()\n");
}


void tcp_accept_handler(void *arg){
    int sock,i;
    struct sockaddr_in sin;
    socklen_t socklen;
    struct conn *conn;
	int local;
    char *c;

    /*dbg("tcp_accept_handler()\n");*/

    socklen = sizeof(sin);
    sock = accept(gnet->tcpsock, (struct sockaddr *)&sin, &socklen);
    if (!socklen || sock<0) return;



	local = ziface_is_local(sin.sin_addr);
    trace(cfg->trace_sock, "Accepted socket %d %s:%d (%s)\n", sock, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), local ? "local" : "non local");
    dbg("Accepted socket %d %s:%d (%s)\n", sock, inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), local ? "local" : "non local");
    log_addf(VTEXT(T_ACCEPTED_SD), inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), local ? "local" : "non local");

    for (i=0;i<gnet->peers->len;i++){
        conn=(struct conn *)g_ptr_array_index(gnet->peers, i);
        if (conn->sock!=sock) continue;
        if (conn->timer) zselect_timer_kill(zsel, conn->timer);
        remove_conn_timer(conn);
    }

    conn = g_new0(struct conn, 1);
    conn->sin.sin_family      = AF_INET;
    conn->sin.sin_addr.s_addr = sin.sin_addr.s_addr;
    conn->sin.sin_port        = sin.sin_port;
    conn->sock = sock;
	conn_set_fid(conn);


    tcp_set_state(conn, NS_CONNECTED);
    zselect_set(zsel, conn->sock, tcp_read_handler,
            NULL, tcp_exception_handler, conn);

    g_ptr_array_add(gnet->peers, conn);
	conn->start = time(NULL);

    dump_all_sources(ctest);

    if (!local) {
        conn->salt = g_strdup_printf("%02X%02X%02X%02X%02X%02X%02X%02X",
                (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff,
                (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff, (unsigned char)rand() % 0xff);
        c = g_strdup_printf("AR %s\n", conn->salt);
        rel_write(conn, c);
        g_free(c);
        conn->authstate = AS_WAIT;
        return;
    }


    dbg("tcp_accept_handler: net_send_id() sock=%d\n", conn->sock);
    net_send_id();
    net_send_ac();
    net_send_operator();
    net_send_read_write_bands();
	net_send_talk();
}



void tcp_connected_handler(void *arg){
    struct conn *conn;
	int ret;
    //int ret, err;
    //socklen_t optlen;

    conn = (struct conn *)arg;
    dbg("tcp_connected_handler %s:%d (%p)\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port), conn);

    /*optlen = sizeof(err);
    ret = getsockopt(conn->sock, SOL_SOCKET, SO_ERROR, (void *)&err, &optlen);*/
/*    dbg("   getsockopt(SO_ERROR) returns %d err is %d\n", ret, err);

    if (ret!=0){
        tcp_disconnect(conn);
        return;
    }
    if (err!=0){
        tcp_disconnect(conn);
        return;
    }	*/
	ret = z_sock_error(conn->sock);
	if (ret != 0){
		tcp_disconnect(conn);
		return;
	}
    log_addf(VTEXT(T_CONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
	trace(cfg->trace_sock || cfg->trace_recv || cfg->trace_send, "%s: connected to %s:%d", conn->fid, inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
    dump_all_sources(ctest);
    tcp_set_state(conn, NS_CONNECTED);
    zselect_set(zsel, conn->sock, tcp_read_handler,
            NULL, tcp_exception_handler, conn);
	conn->start = time(NULL);


    dbg("tcp_connected_handler: net_send_id() sock=%d\n", conn->sock);
    net_send_id();
    net_send_ac();
    net_send_operator();
    net_send_read_write_bands();
	net_send_talk();

}


void tcp_read_handler(void *arg){
    struct conn *conn;
    char s[1030];
    int ret,err;
    gchar *d, *line;
    char errbuf[256];
/*    char ss[60];
    int i;*/


    conn = (struct conn *)arg;
/*    dbg("tcp_read_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
//    dbg("entry tcp_read_handler(%d)\n", conn->sock);

    ret=recv(conn->sock, s, 1024, 0);
    err=z_sock_errno;
    //dbg("   read(%d) returns %d (%d)\n", conn->sock, ret, err);

    if (ret<=0) {
        trace(cfg->trace_sock, " !!! read ERROR sock=%d err=%d %s\n", conn->sock, err, z_sock_strerror());
        dbg(" !!! read ERROR sock=%d err=%d %s\n", conn->sock, err, z_sock_strerror());
        tcp_disconnect(conn);
        return;
    }

	conn->rx += ret;
    s[ret]='\0';
    /*safe_strncpy(ss, s, sizeof(ss));
    for (i=0;ss[i]!='\0'; i++){
        if (!isprint((unsigned char)ss[i])) ss[i]='.';
    }
    if (cfg->trace_recv && strncmp(ss, "AK", 2) != 0 && strncmp(ss, "KA", 2) != 0){
        trace(cfg->trace_recv, "%s: read '%s' (%d)\n", conn->fid, ss, ret);
    }*/

    if (!conn->rdbuf) conn->rdbuf = g_string_sized_new(1024);
    g_string_append(conn->rdbuf, s);

    /* na tomhle to pada */
/*    strcpy(s, "ID 192.168.1.132:55556\nAC 20060506;OK1KRQ;II. subregionalni zavod 2006;cegk\nLA 192.168.1.68:55555;1146999049;192.168.1.132:55555;1146991645;192.168.1.132:55556;1147001847;192.168.1.111:55555;1146932813;192.168.1.82:55555;1147003677;192.168.1.42:55555;1147002456;\n");*/
/*    dbg("   s='%s'\n", s);*/
/*    dbg_recv("tcp_read_handler() entry conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);*/
    while (1){
/*        dbg_recv("tcp_read_handler() 1 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);*/
        if (conn->sock < 0) break;
        d=strchr(conn->rdbuf->str, '\n');
        if (!d) break;
        line = g_strndup(conn->rdbuf->str, d - conn->rdbuf->str);
/*        if (strncmp(line, "KA", 2)!=0 && strncmp(line,"AK", 2)!=0){
            dbg("tcp_read_handler line='%s'\n", line);
        }*/
/*        dbg_recv("tcp_read_handler() 2 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);*/
        g_string_erase(conn->rdbuf, 0, d - conn->rdbuf->str + 1);
/*        dbg_recv("tcp_read_handler() 3 conn->rdbuf=%p '%s'\n", conn->rdbuf, conn->rdbuf->str);*/
/*        dbg("   line='%s', remains '%s'\n", line, conn->rdbuf->str);*/
        if (strlen(line)>0) rel_read(conn, line);
        g_free(line);
    }
/*    dbg_recv("tcp_read_handler() exit conn->rdbuf=%p\n", conn->rdbuf);*/
//    dbg("exit tcp_read_handler(%d)\n", conn->sock);
}


void tcp_write_handler(void *arg){
    struct conn *conn;
    int towrite, written;
    char errbuf[1030];

    conn = (struct conn *)arg;
/*    dbg("tcp_write_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));*/

    if (!conn->wrbuf){
        zselect_set(zsel, conn->sock, tcp_read_handler,
                NULL, tcp_exception_handler, conn);
        return;
    }
    towrite = strlen(conn->wrbuf->str);
    written = send(conn->sock, conn->wrbuf->str, towrite, 0);
    int err=z_sock_errno;
    //trace(cfg->trace_send, "%s: tcp_write_handler '%s' (%d/%d) %s\n", conn->fid, conn->wrbuf->str, towrite, written,err);
    /*dbg("    towrite=%d  written=%d \n", towrite, written); */
    if (written<=0){
        trace(cfg->trace_sock, "!!!!! ERROR %s\n", z_sock_strerror());
        tcp_disconnect(conn);
        return;
    }
	conn->tx += written;
    if (towrite == written){
        g_string_free(conn->wrbuf, 1);
        conn->wrbuf = NULL;
        zselect_set(zsel, conn->sock, tcp_read_handler,
                NULL, tcp_exception_handler, conn);
        return;

    }
    g_string_erase(conn->wrbuf, 0, written);

}


void tcp_exception_handler(void *arg){
    struct conn *conn;

    conn = (struct conn *)arg;
    dbg("tcp_exception_handler %s:%d\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));

    tcp_disconnect(conn);

}

/*************************** TIMERS *********************************/

void udp_sendto(struct sockaddr_in *asin, int direct){
    struct net *net;
    struct sockaddr_in sin;
    socklen_t socklen;
    int i;
    GString *gs;
    time_t now;


    net = (struct net *)gnet;

    now = time(NULL);
    if (gnet->global_expire < now){ /* global expired */
        gnet->global.sin_family      = AF_INET;
        gnet->global.sin_addr.s_addr = gnet->my.sin_addr.s_addr;
        gnet->global.sin_port        = gnet->my.sin_port;
        gnet->global_expire          = now + NET_GLOBAL_EXPIRE;
		gnet->global_priority        = cfg->net_masterpriority;
        trace(cfg->trace_bcast, "expired, i'm new master %s:%d now=%d, expire=%d \n", inet_ntoa(gnet->global.sin_addr), ntohs(gnet->global.sin_port), now%1000, gnet->global_expire%1000);
    }


    if (cmp_sin(&gnet->global, &gnet->my)==0) {  /* sending maximal time */
/*        dbg("setting expire to maximal\n");*/
        gnet->global_expire          = now + NET_GLOBAL_EXPIRE;
    }

  /*  dbg("udp_timer()\n"); */

/*     0     1      2     3      4         5          6            7         8        */
/* "tucnak;version;myip;myport;masterip;masterport;masterexpire;direct;masterpriority */
    gs = g_string_sized_new(100);
    g_string_append_printf(gs, "tucnak;%s;", VERSION_STRING);
    g_string_append(gs, inet_ntoa(gnet->my.sin_addr));
    g_string_append_c(gs,';');

    g_string_append_printf(gs, "%d;", htons(gnet->my.sin_port));

    g_string_append_printf(gs, "%s;%d;%d", inet_ntoa(gnet->global.sin_addr),
                                    ntohs(gnet->global.sin_port),
                                    (int)gnet->global_expire);
    g_string_append_printf(gs, ";%d", (int)now);

    g_string_append_printf(gs, ";%d", direct);

    if (net->v3compatibility)
    	g_string_append_printf(gs, ";");
    else
		g_string_append_printf(gs, ";%d", gnet->global_priority);



/*    dbg("snd'%s' now=%d ex=%d\n", gs->str, now%1000, gnet->global_expire%1000);*/
    trace(cfg->trace_bcast, "send: %s\n", gs->str);
    g_string_append(gs,"\r\n");


    if (asin != NULL){
        socklen = sizeof(sin);
        sendto(gnet->udpsock, gs->str, strlen(gs->str), 0, (struct sockaddr *)asin, socklen);
    }else{
		/*ST_START();
		free_net_ifaces(net);
		init_net_ifaces(net, 0);
		ST_STOP("init_net_ifaces");*/

        for (i=0; i<gnet->max_addrs; i++){
            socklen = sizeof(sin);
            sendto(gnet->udpsock, gs->str, strlen(gs->str), 0, gnet->bcast_addr+i, socklen);
        }
    }

    g_string_free(gs, 1);

}

void udp_timer(void *arg){
#ifdef Z_ANDROID
    udp_sendto(NULL, 1);
#else
    udp_sendto(NULL, 0);
#endif
    gnet->udptimer_id = zselect_timer_new(zsel, gnet->udptimer_period, udp_timer, gnet);
}


void send_ack_timer(void *arg){
    struct conn *conn;
    char s[1026];

    conn = (struct conn *)arg;
    /*dbg("send_ack_timer %s:%d\n", inet_ntoa(conn->sin.sin_addr),
     * ntohs(conn->sin.sin_port));*/
    conn->timer = 0;

    g_snprintf(s, 1024, "KA %d\n", ++conn->relseq);
    rel_write(conn, s);
    tcp_set_state(conn, NS_WAIT_ACK);
}

void kill_conn_timer(void *arg){
    struct conn *conn;

    conn = (struct conn *)arg;
/*    dbg("kill_conn_timer %s:%d\n", inet_ntoa(conn->sin.sin_addr),
 *    ntohs(conn->sin.sin_port));*/
    conn->timer = 0;
    tcp_disconnect(conn);
}

void remove_conn_timer(void *arg){
    struct conn *conn, *cx;
    int i;

    conn = (struct conn *)arg;
    dbg("remove_conn_timer %s:%d (%p)\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port), conn);
    conn->timer = 0;

    if (gnet->master == conn) {
        free_conn(gnet->master);
        g_free(gnet->master);
        gnet->master = NULL;
    }

    if (gnet->remote == conn) {
        dbg("freeing remote\n");
        free_conn(gnet->remote);
        g_free(gnet->remote);
        gnet->remote = NULL;
        net_connect_remote(gnet);
    }

    for (i=gnet->peers->len-1; i>=0; i--){
        cx = (struct conn *) g_ptr_array_index(gnet->peers, i);
        if (cx == conn) {
            g_ptr_array_remove_index(gnet->peers, i);
            free_conn(conn);
            g_free(conn);
        }
    }

}


int conn_prod_state(struct conn *conn){
    if (!conn) return 0;
    if (conn->state == NS_CONNECTED || conn->state == NS_WAIT_ACK || conn->state == NS_LONG) return 1;
    return 0;
}

void tcp_set_state(struct conn *conn, enum net_state state){
    int debug = 0;

    if (conn->timer) zselect_timer_kill(zsel, conn->timer);
    conn->timer = 0;
    if (debug) dbg("tcp_set_state %s ", tcp_state_s(conn));
    conn->state = state;
    if (debug) dbg("-> %s\n", tcp_state_s(conn));
    switch (conn->state){
        case NS_INIT:
            break;
        case NS_CONNECTING:
            conn->timer = zselect_timer_new(zsel, CONNECTION_TIMEOUT, kill_conn_timer, conn);
            break;
        case NS_CONNECTED:
            conn->timer = zselect_timer_new(zsel, SEND_ACK_TIMEOUT, send_ack_timer, conn);
            break;
        case NS_WAIT_ACK:
            conn->timer = zselect_timer_new(zsel, ACK_TIMEOUT, kill_conn_timer, conn);
            break;
        case NS_DISCONNECTED:
            conn->timer = zselect_timer_new(zsel, REMOVE_TIMEOUT, remove_conn_timer, conn);
            break;
        case NS_DEAD:
            break;
		case NS_LONG:
			conn->timer = zselect_timer_new(zsel, LONG_TIMEOUT, kill_conn_timer, conn);
			break;
    }
}

char *tcp_state_s(struct conn *conn){
    switch (conn->state){
        case NS_INIT:
            return VTEXT(T_INIT);
        case NS_CONNECTING:
            return VTEXT(T_CONNECTING);
        case NS_CONNECTED:
            return VTEXT(T_CONNECTED);
        case NS_WAIT_ACK:
            return VTEXT(T_WAIT_ACK);
        case NS_DISCONNECTED:
            return VTEXT(T_DISCONNECTED2);
        case NS_DEAD:
            return VTEXT(T_DEAD);
		case NS_LONG:
			return VTEXT(T_LONG_XFER);
    }
    return "???";
}

void tcp_connect(struct conn *conn){
    int ret;
    char errbuf[256];

    dbg("tcp->connect %s:%d (%p)\n", inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port), conn);

    conn->sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    dbg("tcp_connect(socket() = %d \n", conn->sock);

    if (z_sock_nonblock(conn->sock, 1)){
        trace(cfg->trace_sock, "Can't set O_NONBLOCK\n");
        return;
    }

	conn->start = time(NULL);

    ret = connect(conn->sock, (struct sockaddr *) &conn->sin, sizeof(struct sockaddr_in));
/*    dbg("connect returns %d (%d) '%s'\n", ret, errno, strerror_r(errno));*/
    if (ret==0){
        zselect_set(zsel, conn->sock, tcp_read_handler, NULL, tcp_exception_handler, conn);
        tcp_set_state(conn, NS_CONNECTED);
        log_addf(VTEXT(T_CONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
        dump_all_sources(ctest);
        dbg("tcp_connect: net_send_id() sock=%d\n", conn->sock);
        net_send_id();
        net_send_ac();
        net_send_operator();
        net_send_read_write_bands();
		net_send_talk();
        return;
    }
    if (ret<0){
        int err = z_sock_errno;
        if (z_sock_wouldblock(z_sock_errno)){
            tcp_set_state(conn, NS_CONNECTING);
            zselect_set(zsel, conn->sock, NULL, tcp_connected_handler, tcp_exception_handler, conn);
            log_addf(VTEXT(T_CONNECTING_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
            return;
        }
        trace(cfg->trace_sock, "connect error %s\n", z_sock_strerror());
        tcp_disconnect(conn);
    }

}

#ifdef LEAK_DEBUG_LIST
void debug_tcp_disconnect(char *file, int line, struct conn *conn){
    dbg("debug_tcp_disconnect(%s, %d) conn->sock==%d\n", file, line, conn->sock);
#else
void tcp_disconnect(struct conn *conn){
    dbg("tcp_disconnect conn->sock=%d %s:%d (%p)\n", conn->sock, inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port), conn);
#endif
    log_addf(VTEXT(T_DISCONNECTED_SD), inet_ntoa(conn->sin.sin_addr), ntohs(conn->sin.sin_port));
    tcp_set_state(conn, NS_DISCONNECTED);
    if (conn->sock <0){
        dbg("!!! tcp_disconnect called on conn->sock=%d, ignoring\n", conn->sock);
    }else{
//        dbg("zselect_set(zsel, %s, %d)\n", __FILE__, __LINE__);
        zselect_set(zsel, conn->sock, NULL, NULL, NULL, NULL);
    }
	trace(cfg->trace_sock || cfg->trace_recv || cfg->trace_send, "%s: close tcp_disconnect", conn->fid);
    //dbg("tcp_disconnect(%d)\n", conn->sock);
 //   dbg("tcp_disconnect(%d) @ %s:%d\n", conn->sock, file, line);
    dump_all_sources(ctest);
    if (conn->sock >= 0){
        closesocket(conn->sock);
    }
    conn->sock = -1;
    if (conn->wrbuf) g_string_free(conn->wrbuf, 1);
    if (conn->rdbuf) g_string_free(conn->rdbuf, 1);
    conn->wrbuf = conn->rdbuf = NULL;
#ifdef LEAK_DEBUG_LIST
    dbg("debug_tcp_disconnect return\n");
#else
    dbg("tcp_disconnect return\n");
#endif
}

void tcp_kill(struct conn *conn){
    /*dbg("tcp->disconnect %s:%d\n", inet_ntoa(conn->sin.sin_addr),
     * ntohs(conn->sin.sin_port));*/
    zselect_set(zsel, conn->sock, NULL, NULL, NULL, NULL);
    tcp_set_state(conn, NS_DISCONNECTED);
    trace(cfg->trace_sock, "%s: close tcp_kill", conn->fid);
    dump_all_sources(ctest);
    closesocket(conn->sock);
}

int cmp_sin(struct sockaddr_in *a, struct sockaddr_in *b){
    int sign;

    sign = ntohl(a->sin_addr.s_addr) - ntohl(b->sin_addr.s_addr);
    if (sign) return sign;
    sign = ntohs(a->sin_port) - ntohs(b->sin_port);
    return sign;
}

int cmp_master(int pria, int prib, struct sockaddr_in *a, struct sockaddr_in *b){
    int sign;
	//char *s;

    if (gnet->v3compatibility == 0){
        sign = pria - prib;
    }else{
        sign = 0;
    }

    if (sign == 0) {
		sign = ntohl(a->sin_addr.s_addr) - ntohl(b->sin_addr.s_addr);
		if (sign == 0){
			sign = ntohs(a->sin_port) - ntohs(b->sin_port);
		}
	}
	/*s = g_strdup(inet_ntoa(b->sin_addr));
	if (gnet->v3compatibility == 0)
		dbg("cmp_master(%d/%s:%d, %d/%s:%d) = %d\n", pria, inet_ntoa(a->sin_addr), ntohs(a->sin_port), prib, s, ntohs(b->sin_port), sign);
	else
    dbg("cmp_master(%s:%d, %s:%d) = %d\n", inet_ntoa(a->sin_addr), ntohs(a->sin_port), s, ntohs(b->sin_port), sign);
	g_free(s);*/
    return sign;
}

/***************** "RELATION" LAYER ********************************/

void rel_write(struct conn *conn, gchar *s){
    int towrite, written,err;
    GString *oldwrbuf;
    char errbuf[1030];

    if (conn->sock < 0) return; /* socket already closed, ignore write request and return to allow free conn */

	if (conn->authstate == AS_WAIT && strncmp(s, "BC", 2) != 0) return;

    oldwrbuf=conn->wrbuf;
    if (!conn->wrbuf) conn->wrbuf = g_string_sized_new(1024);
    g_string_append(conn->wrbuf, s);

    if (oldwrbuf){
        /* conn->wrbuf!=NULL at start of rel_write means there is unsent data.
         * It's send in tcp_write_handler */
        ///trace(cfg->trace_send, "%s: rel_write '%s' appended to wrbuf");
        return;
    }

    towrite = strlen(conn->wrbuf->str);
    written = send(conn->sock, conn->wrbuf->str, towrite, 0);
    err = z_sock_errno;
    if (cfg->trace_send && strncmp(s, "AK", 2) != 0 && strncmp(s, "KA", 2) != 0
#ifdef CLEAN_TRACE
		&& strncmp(s, "WI", 2) != 0 && strncmp(s, "WT", 2) != 0
#endif
		){
		char ss[260], *cc;
		int ll;
		g_strlcpy(ss, s, sizeof(ss));
		ll = strlen(ss);
		if (ll > 0 && ss[ll-1] == '\n') ss[ll-1] = '\0';
		for (cc = ss; *cc != '\0'; cc++) if (!isprint((unsigned char)*cc)) *cc = '.';
        trace(cfg->trace_send, "%s: rel_write '%s' (%d/%d/%d)", conn->fid, ss, towrite, written, err);
    }
    if (written<0){
		if (z_sock_wouldblock(err)){
            zselect_set(zsel, conn->sock, tcp_read_handler,
                    tcp_write_handler, tcp_exception_handler, conn);
            return;
        }
        trace(cfg->trace_sock, "error writing %s\n", z_sock_strerror());
        tcp_disconnect(conn);
        return;
    }
	conn->tx += written;

    if (towrite == written){
        g_string_free(conn->wrbuf, 1);
        conn->wrbuf = NULL;
        return;
    }
    g_string_erase(conn->wrbuf, 0, written);
    zselect_set(zsel, conn->sock, tcp_read_handler,
            tcp_write_handler, tcp_exception_handler, conn);

}

void rel_write_all(gchar *s){
    int i;
    struct conn *conn;
    /*char ss[256];

    strncpy(ss, s, 2);
    ss[2]='\0';*/

    if (gnet->master && conn_prod_state(gnet->master)) {
        /*dbg("rel_write_all('%s') %d(%d)", ss, gnet->master->sock, tcp_state_s(gnet->master));*/
        rel_write(gnet->master, s);
    }

	if (gnet->remote && conn_prod_state(gnet->remote)) {
        /*dbg("rel_write_all('%s') %d(%d)", ss, gnet->remote->sock, tcp_state_s(gnet->remote));*/
        rel_write(gnet->remote, s);
    }

    /*dbg("rel_write_all('%s') ",ss);*/
    for (i=0; i<gnet->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(gnet->peers, i);
        if (conn_prod_state(conn)) {
            /*dbg("%d(%s) ", conn->sock,tcp_state_s(conn) );*/
            rel_write(conn, s);
        }
    }
/*    dbg("\n"); */
}

/* writes to all almost conn */
void rel_write_almost_all(struct conn *almost_conn, gchar *s){
    int i;
    struct conn *conn;

/*    dbg("rel_write_almost_all(%d, '%s')\n", almost_conn->sock, s);  */
    if (gnet->master && conn_prod_state(gnet->master) && gnet->master != almost_conn){
        rel_write(gnet->master, s);
      //  return;
    }

    if (gnet->remote && conn_prod_state(gnet->remote) && gnet->remote != almost_conn){
        rel_write(gnet->remote, s);
    //    return;
    }

    for (i = 0; i < gnet->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(gnet->peers, i);
        if (conn_prod_state(conn) && conn != almost_conn) {
            rel_write(conn, s);
        }
    }
}

struct conn *find_conn_by_remote_id(gchar *remote_id){
    int i;
    struct conn *conn;

    for (i=gnet->peers->len-1;i>=0;i--){ /* backward loop because one ip+port can be here twice (DIST,DIST,DIST,CONN) */
        conn=(struct conn *)g_ptr_array_index(gnet->peers, i);
        if (!conn->remote_id) continue;
        if (strcmp(conn->remote_id,remote_id)==0){
            return conn;
        }
    }
    return NULL;
}

int net_route(gchar *s, gchar **items){
    struct conn *conn;
    gchar *c;

    if (!items[0]) return -1;

    if (strcmp(gnet->myid,items[0])==0) return 0; /* packet is for me */

    conn = find_conn_by_remote_id(items[0]);
    if (!conn || !conn_prod_state(conn)) return -1;

    c=g_strconcat(s,"\n",NULL);
/*    dbg("routing '%s'\n",c);*/
    rel_write(conn, c);
    g_free(c);
    return 1;
}

static struct conn *sconn; /* a little hack with global var :-( */

void rel_read(struct conn *conn, gchar *line){
    char s[1026], ss[260], *cc;
    gchar **items, *c;


//  dbg("rel_read(%d) '%s'\n", conn->sock, line);

    if (strncmp(line, "KA", 2)==0){    /* keepalive */
/*        dbg("   recvd KA, sending AK\n");*/

        g_snprintf(s, 1024, "AK%s\n", line+2);
        rel_write(conn, s);
        return;
    }

    if (strncmp(line, "AK", 2)==0){   /* keepalive ack */
/*        dbg("   recvd AK, setting NS_CONNECTED\n");*/
        /* todo AK number */
        tcp_set_state(conn, NS_CONNECTED);

        return;
    }

  /************** app layer ********************/

	if (cfg->trace_recv
#ifdef CLEAN_TRACE
		&& strncmp(line, "WI", 2) != 0 && strncmp(line, "WT", 2) != 0
#endif
		){
		g_strlcpy(ss, line, sizeof(ss));
		for (cc = ss ; *cc != '\0'; cc++)  if (!isprint((unsigned char)*cc)) *cc = '.';

		trace(cfg->trace_recv, "%s: rel_read  '%s' (%d)\n", conn->fid, ss, strlen(ss));
	}


    if (strncmp(line, "AR", 2) == 0){     /* authentication request */
        char *hash, *str;
        char *pcall;
        struct zmd5 md5;

        if (strlen(line) < 4) return;
        c = line + 3;

        items = g_strsplit(c, ";", 0);
        if (!items || !items[0]) return;

        pcall = cfg->pcall;
        if (ctest) pcall = ctest->pcall;
        hash = g_strdup_printf("%s;%s;%s", pcall, cfg->net_remote_pass, items[0]);
        zmd5_init(&md5);
        zmd5_update(&md5, (unsigned char*)hash, strlen(hash));
        zmd5_final_str(&md5);
        //log_addf("AR hash='%s' %s", hash, md5.tdigest);

        str = g_strdup_printf("AU %s;%s\n", pcall, md5.tdigest);
        rel_write(conn, str);
        g_free(str);
        g_free(hash);
        g_strfreev(items);

		net_send_id();
		net_send_ac();
		net_send_operator();
		net_send_read_write_bands();
		net_send_talk();
        return;
    }

    if (strncmp(line, "AU", 2) == 0){     /* authenticate */
        char *hash, *pcall;
        struct zmd5 md5;

        if (strlen(line) < 4) return;
        c = line + 3;

        items = g_strsplit(c, ";", 0);
        if (!items || !items[0] || !items[1]) return;

        pcall = cfg->pcall;
        if (ctest) pcall = ctest->pcall;

        hash = g_strdup_printf("%s;%s;%s", pcall, cfg->net_remote_pass, conn->salt);
        zmd5_init(&md5);
        zmd5_update(&md5, (unsigned char *)hash, strlen(hash));
        zmd5_final_str(&md5);
        //log_addf("AU hash='%s' %s", hash, md5.tdigest);

        if (strcasecmp(items[0], pcall) != 0){
            c = g_strdup_printf("BC user %s != %s\n", items[0], pcall);
            rel_write(conn, c);
            g_free(c);
            log_addf(VTEXT(T_BAD_AUTH_USER), items[0], pcall);
            g_strfreev(items);
            return;
        }

        if (strcasecmp(items[1], (char *)md5.tdigest) != 0){
            //c = g_strdup_printf("BC Bad auth pass %s != %s\n", items[1], md5.tdigest);
            c = g_strdup_printf("BC passord\n");
            rel_write(conn, c);
            g_free(c);
            log_addf(VTEXT(T_BAD_AUTH_PASS), items[1], md5.tdigest);
            g_strfreev(items);
            return;
        }
		log_addf(VTEXT(T_AUTHENTICATED_USER_S), items[0]);
        tcp_set_state(conn, NS_CONNECTED);
		conn->authstate = AS_OK;
        g_strfreev(items);

        net_send_id();
        net_send_ac();
        net_send_operator();
        net_send_read_write_bands();
		net_send_talk();
        return;
    }

    if (strncmp(line, "BC", 2) == 0){
        if (strlen(line) < 4) return;
        c = line + 3;

        log_addf(VTEXT(T_BAD_AUTHENTICATION_S), c);
        return;
    }

	if (conn->authstate == AS_WAIT) return;

    if (strncmp(line, "ID", 2)==0){     /* peers ID */
/*        log_addf("%d ID '%s'",conn->sock, line); */
        if (strlen(line)<4) return;
        c = line+3;

        if (conn->remote_id) g_free(conn->remote_id);
        conn->remote_id = g_strdup(c);
        return;
    }


    if (strncmp(line, "AC", 2)==0){     /* active contest */
/*        log_addf("%d AC '%s'",conn->sock, line); */
        if (strlen(line)<4) return;
        c = line+3;

        if (conn->remote_ac) g_free(conn->remote_ac);
        conn->remote_ac = g_strdup(c);

        if (!ctest) return;

        net_test_same_contest(conn, c);
        return;
    }

    if (strncmp(line, "CL", 2)==0){       /* close contest */
/*        log_addf("%d CL", conn->sock);*/
        if (conn->remote_ac) {
            g_free(conn->remote_ac);
            conn->remote_ac = NULL;
        }
        conn->is_same_ctest = 0;
        return;
    }

    if (strncmp(line, "LA", 2)==0){      /* latest qsos for each source */
        int i;
        GHashTable *remote_latests;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<3) return;
        c = line+3;

        remote_latests = g_hash_table_new(g_str_hash, g_str_equal);

        items=g_strsplit(c, ";", 0);
        for (i=0; items[i]!=NULL && items[i+1]!=NULL; i+=2){
            gchar *source, *latest_s;

            source = items[i];
            latest_s = items[i+1];
            g_hash_table_insert(remote_latests, g_strdup(source), g_strdup(latest_s));
            trace(cfg->trace_sock, "  rcvd LA: %s = %d\n", source, latest_s);
        }
        g_strfreev(items);

        /*dbg("LA recvd sock=%d:\n", conn->sock);*/
        dbg_str_hash(remote_latests);
/*        dbg("ctest->bystamp:\n");
        dbg_str_hash(ctest->bystamp);*/
        sconn = conn;
        rel_write(conn, "SR\n");
        g_hash_table_foreach(ctest->bystamp, compare_remote_with_me, remote_latests);
        z_ptr_array_qsort(ctest->allqsos, compare_date_time_qsonrs);
        rel_write(conn, "ER\n");

        g_hash_table_foreach_remove(remote_latests, free_gpointer_item, NULL);
        g_hash_table_destroy(remote_latests);

		net_send_operator();
        net_send_read_write_bands();
		net_send_talk();
        return;
    }
    if (strncmp(line, "QS", 2)==0){ /* qso */
        if (!ctest) {
            trace(cfg->trace_sock, "!!! QS recvd, ctest==NULL!! \n");
            return;
        }
        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c = line+3;
        qso_from_net(conn, c);
        return;
    }

    if (strncmp(line, "SR", 2)==0){ /* start of replication */
        if (!conn->is_same_ctest) return;
        conn->replicating=1;
        return;
    }

    if (strncmp(line, "ER", 2)==0){ /* end of replication */
		if (!ctest) return;
        if (!conn->is_same_ctest) return;
        conn->replicating=0;
        recalc_all_stats(ctest); /* include statsfifo */
        /*check_autosave();*/
        return;
    }

    if (strncmp(line, "TA", 2)==0){ /* talk */
        gchar *d;

        if (strlen(line)<4) return;
        c = line+3;

        sw_talk_read(c, 0);
        d=g_strconcat("TA ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        return;
    }

    if (strncmp(line, "TB", 2)==0){ /* talk after connect */
        gchar *d;

        if (strlen(line)<4) return;
        c = line+3;

        if (ctest) {
			gtalk->withouttime = 1;
			sw_talk_read(c, 1);
			gtalk->withouttime = 0;
		}

        d=g_strconcat("TB ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        return;
    }

    if (strncmp(line, "SK", 2)==0){ /* sked */
        gchar *d;
        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c = line+3;

        /*dbg("SK conn=%p\n", conn);*/
        sw_sked_read(c, 0);
        d=g_strconcat("SK ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        return;
    }

    if (strncmp(line, "DI", 2)==0){ /* OBSOLETE peers discovery */
        int i;
        GString *gs;
        struct conn *tmpconn;

        /*dbg("DI recvd\n");*/
        gs =g_string_sized_new(100);
        g_string_append(gs,"PE ");

        g_string_append_printf(gs, "%s;", gnet->myid);

        for (i=0;i<gnet->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(gnet->peers,i);

            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_id) return;
            g_string_append_printf(gs, "%s;", tmpconn->remote_id);

        }
        g_string_append_printf(gs,"\n");
        trace(cfg->trace_sock, "sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }

    if (strncmp(line, "PE", 2)==0){ /* OBSOLETE peers reply */
         /* TODO security - only if required */

        if (strlen(line)<4) return;
        c = line+3;

        trace(cfg->trace_sock, "PEers '%s'\n", c);
        CONDGFREE(gnet->allpeers);
        gnet->allpeers=g_strdup(c);

        if (!gnet->peerfunc) return;
        do_peer_menu((void (*)(void *arg)) gnet->peerfunc);
        gnet->peerfunc=NULL;
        return;
    }

    if (strncmp(line, "CR", 2)==0){ /* configuration request */
        gchar *d,*cc;
        GString *gs;
        char *token_ptr=NULL;
        //int tmpi;

        if (strlen(line)<4) return;
        c = line+3;


        items=g_strsplit(c, ";", 0);
        if (net_route(line,items)) return;
        if (!items[0] || !items[1]) return; /* dst,src */

        gs=g_string_sized_new(10000);
        save_rc_string(gs);

        /* sets this socket temporarily to blocking mode */
        z_sock_nonblock(conn->sock, 0);
        //tmpi=0;
        for (cc=strtok_r(gs->str,"\n",&token_ptr); cc!=NULL; cc=strtok_r(NULL,"\n",&token_ptr)){
/*            if (tmpi++==4) break; */ /* TODO FIXME */
            d=g_strdup_printf("CF %s;%s;%s\n",items[1],items[0],cc);
            rel_write(conn,d);
            g_free(d);
        }

        d=g_strdup_printf("CF %s;%s;eof\n",items[1],items[0]);
        rel_write(conn,d);

        z_sock_nonblock(conn->sock, 1);

        g_free(d);
        g_string_free(gs,TRUE);
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "CF", 2)==0){ /* configuration line */
        gchar *d;

        if (strlen(line)<4) return;
        c = line+3;


        items=g_strsplit(c, ";", 2);
        if (net_route(line,items)) goto x_cf;

        if (!items[0] || !items[1] || !items[2]) goto x_cf; /* dst,src */


        d = strchr(items[2],'#');
        if (d) *d='\0';
        g_strstrip(items[2]);
        if (strlen(items[2])==0) goto x_cf;

        if (strcmp(items[2],"eof")==0){
            log_addf(VTEXT(T_LOADED_CFG_SSSS),cfg->pcall, cfg->pwwlo, cfg->padr1,cfg->padr2);
#ifdef AUTOSAVE
            menu_save_rc(NULL);
#endif
            goto x_cf;
        }

        d = g_strconcat(items[2],"\n",NULL);
/*        dbg("doing something with '%s'\n",items[2]);*/
        read_rc_line(d);
        g_free(d);
x_cf:;
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "WR", 2)==0){ /* C_W request */
        gchar *d,*cc;
        GString *gs;
        char *token_ptr=NULL;
//        int tmpi;

        if (strlen(line)<4) return;
        c = line+3;


        items=g_strsplit(c, ";", 0);
        if (net_route(line,items)) return;
        if (!items[0] || !items[1]) return; /* dst,src */

        gs=g_string_sized_new(10000);
        save_cw_string(cw,gs);

        /* sets this socket temporarily to blocking mode */
        z_sock_nonblock(conn->sock, 0);
//        tmpi=0;
        for (cc=strtok_r(gs->str,"\n",&token_ptr); cc!=NULL; cc=strtok_r(NULL,"\n",&token_ptr)){
/*            if (tmpi++==4) break; */ /* TODO FIXME */
            d=g_strdup_printf("CW %s;%s;%s\n",items[1],items[0],cc);
            rel_write(conn,d);
            g_free(d);
        }

        d=g_strdup_printf("CW %s;%s;eof\n",items[1],items[0]);
        rel_write(conn,d);

        z_sock_nonblock(conn->sock, 1);

        g_free(d);
        g_string_free(gs,TRUE);
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "CW", 2)==0){ /* C_W data */

        if (strlen(line)<4) return;
        c = line+3;


        items=g_strsplit(c, ";", 2);
        if (net_route(line,items)) goto x_cw;

        if (!items[0] || !items[1] || !items[2]) goto x_cw; /* dst;src;call loc stamp */

        if (strcmp(items[2],"eof")==0){
            gchar *s;
            int ret;

            log_addf(VTEXT(T_LOADED_CW));

            s = g_strconcat(tucnak_dir, "/tucnakcw", NULL);
			z_wokna(s);
            ret=save_cw_into_file(cw, s);
            if (ret){
                errbox(VTEXT(T_CANT_WRITE), ret);
            }else{
                log_addf(VTEXT(T_SAVED_S), s);
            }
            g_free(s);
            goto x_cw;
        }

/*        dbg("doing something with '%s'\n",items[2]);*/
        load_one_cw(cw,items[2]);
x_cw:;
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "DX", 2)==0){ /* DX spot */
        gchar *d;
		int retdx;

        if (strlen(line)<4) return;
        c = line+3;

        /*dbg("DX conn=%p\n", conn);*/
        retdx = dxc_read_spot(c);
        d=g_strconcat("DX ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
		if (retdx == 0){
			zg_ptr_array_foreach(struct subwin *, sw, gses->subwins){
				if (sw->type != SWT_DXC) continue;
                sw_dxc_spot_from_net(sw, c); // modifies char case
			}
		}
        redraw_later();
        return;
    }

    if (strncmp(line, "RG", 2)==0){ /* QRG for sked */
        gchar *d;
        struct band *b;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 2);
        if (!items[0] || !items[1]) goto x_rg;

        b = find_band_by_pband(items[0]);
        if (!b) {
            trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
            goto x_rg;
        }
        g_free(b->skedqrg);
        b->skedqrg = fixsemi(g_strdup(items[1]));
        b->dirty_save = 1;

        d=g_strconcat("RG ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

x_rg:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "GR", 2)==0){ /* grab band command */
        gchar *d;
        struct band *b;
        enum ccmd ccmd;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_gr;

        b = find_band_by_pband(items[0]);
        if (!b) {
            trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
            goto x_gr;
        }
        ccmd=(enum ccmd)atoi(items[1]);

        net_grab(b,ccmd,items[2]);
        d=g_strconcat("GR ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

x_gr:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "WC", 2)==0){ /* OBSOLETE working callsign */
        return;
    }
    if (strncmp(line, "WT", 2)==0){ /* tmpqso item */
        gchar *d;
        struct band *b;
        struct spypeer *sp;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 4);
        if (!items[0] || !items[1] || !items[2] || !items[3]) goto x_wt;

        b = find_band_by_pband(items[0]);
        if (!b) {
            trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
            goto x_wt;
        }

        if (atoi(items[2])==WT_OPERATOR){
            if (strcmp(items[1], conn->remote_id)==0){
                CONDGFREE(conn->operator_);
                conn->operator_ = g_strdup(items[3]);
            }
        }

        sp = get_spypeer_by_peerid(ctest->spypeers, items[1], z_char_uc(b->bandchar));
        if (sp) {       /* we want spy info from items[1] */

            switch(atoi(items[2])){
                case WT_CLEAR:
                    clear_spypeer(sp);
                    break;
                case WT_CALLSIGN:
                    CONDGFREE(sp->callsign);
                    sp->callsign=g_strdup(items[3]);
                    break;
                case WT_RSTS:
                    CONDGFREE(sp->rsts);
                    sp->rsts=g_strdup(items[3]);
                    break;
                case WT_RSTR:
                    CONDGFREE(sp->rstr);
                    sp->rstr=g_strdup(items[3]);
                    break;
                case WT_QSONRS:
                    CONDGFREE(sp->qsonrs);
                    sp->qsonrs=g_strdup(items[3]);
                    break;
                case WT_QSONRR:
                    CONDGFREE(sp->qsonrr);
                    sp->qsonrr=g_strdup(items[3]);
                    break;
                case WT_EXC:
                    CONDGFREE(sp->exc);
                    sp->exc=g_strdup(items[3]);
                    break;
                case WT_LOCATOR:
                    CONDGFREE(sp->locator);
                    sp->locator=g_strdup(items[3]);
                    break;
                case WT_OPERATOR:
                    CONDGFREE(sp->operator_);
                    sp->operator_=g_strdup(items[3]);
                    break;
				case WT_BAND:
					sp->abandchar = b->bandchar;
                    break;
            }
        }

        d=g_strconcat("WT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();

x_wt:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "PT", 2)==0){ /* peer TX */
        gchar *d;
        struct band *b;
        struct spypeer *sp;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_pt;

        b = find_band_by_pband(items[0]);
        if (!b) {
            trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
            goto x_pt;
        }
        sp = get_spypeer_by_peerid(ctest->spypeers, items[1], z_char_uc(b->bandchar));
        if (sp) {      /* we want spy info from items[1] */
            sp->peertx = atoi(items[2]);
        }

        d=g_strconcat("PT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
        redraw_later();

x_pt:
        g_strfreev(items);
        return;
    }
    if (strncmp(line, "WI", 2)==0){ /* working inputline */
        gchar *d;
        struct band *b;
        struct spypeer *sp;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 3);
        if (!items[0] || !items[1] || !items[2]) goto x_wi;

        b = find_band_by_pband(items[0]);
        if (!b) {
            trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
            goto x_wi;
        }
        sp = get_spypeer_by_peerid(ctest->spypeers, items[1], z_char_uc(b->bandchar));
        if (sp) {       /* we want spy info from items[1] */
            CONDGFREE(sp->inputline);
            sp->inputline = g_strdup(items[2]);
            redraw_later();
        }

        d=g_strconcat("WI ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

x_wi:
        g_strfreev(items);
        return;
    }
	if (strncmp(line, "R3", 2) == 0){ // contest request v3
		char *d, *dstid, *srcid, *lo;
		int i, tok = 0;
		GString *gs;


		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);
		srcid = z_tokenize(c, &tok);
		lo = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			struct zstring *zs;
			int load_opts = 0;

 	 	    if (!ctest) {
				g_free(c);
				return;
			}

			// contest options
			gs = g_string_sized_new(4000);
			zs = contest_format(ctest);
			zg_string_eprintfa("e", gs, "S3 %s;%s", srcid, zs->str);
			zfree(zs);

			for (i = 0; i < ctest->bands->len; i++){
				struct band *b = (struct band*)g_ptr_array_index(ctest->bands, i);
				struct zstring *zs = band_format(b);
				zg_string_eprintfa("e", gs, ";%s", zs->str);
				zfree(zs);
			}
			g_string_append_c(gs, '\n');
			rel_write(conn, gs->str);

			if (lo != NULL) load_opts = atoi(lo);

			if (load_opts & LO_DEF){
				g_string_truncate(gs, 0);
				zs = def_format(cfg);
				zg_string_eprintfa("e", gs, "E3 %s;%s", srcid, zs->str);
				zfree(zs);
				g_string_append_c(gs, '\n');
				rel_write(conn, gs->str);
			}


			// start of transfer
			if (load_opts & (LO_CW | LO_NAME | LO_QRV)){
				char *cc = g_strdup_printf("ST %s\n", srcid);
				rel_write(conn, cc);
				g_free(cc);
				tcp_set_state(conn, NS_LONG);
			}

			// CW database
			if (load_opts & LO_CW){
				g_string_truncate(gs, 0);
				zg_string_eprintfa("e", gs, "CV %s;", srcid);
				format_cw_string(cw, gs);
				g_string_append_c(gs, '\n');
				rel_write(conn, gs->str);
			}

			// Name database
			if (load_opts & LO_NAME){
				g_string_truncate(gs, 0);
				zg_string_eprintfa("e", gs, "NA %s;", srcid);
				format_namedb_string(namedb, gs);
				g_string_append_c(gs, '\n');
				rel_write(conn, gs->str);
			}

			// QRV database
			if (load_opts & LO_QRV){
				g_string_truncate(gs, 0);
				zg_string_eprintfa("e", gs, "QR %s", srcid);
				for (i = 0; i < qrv->qrvs->len; i++){
					struct qrv_item *qi = (struct qrv_item *)z_ptr_array_index(qrv->qrvs, i);
					struct zstring *zs = qrv_format(qi);
					if (i > 0) g_string_append_c(gs, ';');
					zg_string_eprintfa("e", gs, ";%s", zs->str);
					zfree(zs);
				}
				g_string_append_c(gs, '\n');
				rel_write(conn, gs->str);
			}

			// end of transfer
			if (load_opts){
				char *cc = g_strdup_printf("ET %s;%s\n", srcid, gnet->myid);
				rel_write(conn, cc);
				g_free(cc);
			}
			g_string_free(gs, TRUE);

		}else{
			d = g_strconcat("R3 ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
	}
	if (strncmp(line, "S3", 2) == 0){ // conntest options v3
		int tok = 0;
		char *dstid, *d;
        struct zstring *zs;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			if (ctest){
				g_free(c);
				return;
			}
            zs = zstrdup(line + 3 + tok);
            init_ctest2(0, 1, zs, NULL);
            zfree(zs);
		}else{
			d = g_strconcat("S3 ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);

	}
	if (strncmp(line, "E3", 2) == 0){ // conntest defaults
		int tok = 0;
		char *dstid, *d;
		struct zstring *zs;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			if (ctest){
				g_free(c);
				return;
			}
			zs = zstrdup(line + 3 + tok);
			def_parse(zs, cfg);
			zfree(zs);
		}
		else{
			d = g_strconcat("E3 ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);

	}
	if (strncmp(line, "QR", 2) == 0){ // QRV database
		int tok = 0;
		char *it, *dstid, *d;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			while ((it = z_tokenize(c, &tok)) != NULL){
				load_one_qrv(qrv, it);
			}
		}else{
			d = g_strconcat("QR ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "CV", 2) == 0){ // CW database
		int tok = 0;
		char *it, *dstid, *d;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			while ((it = z_tokenize(c, &tok)) != NULL){
				load_one_cw(cw, it);
			}
		}else{
			d = g_strconcat("CV ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "NA", 2) == 0){ // Name database
		int tok = 0;
		char *it, *dstid, *d;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			while ((it = z_tokenize(c, &tok)) != NULL){
				load_one_namedb(namedb, it);
			}
		}else{
			d = g_strconcat("NA ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "ST", 2) == 0){ // start of transfer
		int tok = 0;
		char *dstid, *d;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			tcp_set_state(conn, NS_LONG);
            log_addf(VTEXT(T_LONG_TRANSFER_STARTED_PLEASE_WAIT));
		}else{
			d = g_strconcat("ST ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "ET", 2) == 0){ // end of transfer
		int tok = 0;
		char *dstid, *d, *srcid;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);
		srcid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			tcp_set_state(conn, NS_CONNECTED);
			d = g_strdup_printf("EB %s\n", srcid);
			rel_write(conn, d);
			g_free(d);
            log_addf(VTEXT(T_LONG_TRANSFER_FINISHED));
		}else{
			d = g_strconcat("ET ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "EB", 2) == 0){ // end of transfer back
		int tok = 0;
		char *dstid, *d;

		if (strlen(line) < 2) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
			tcp_set_state(conn, NS_CONNECTED);
		}else{
			d = g_strconcat("EB ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}

	if (strncmp(line, "D3", 2) == 0){ // discover peers v3
		GString *gs;

        if (strlen(line) < 2) return;
        //c = line + 3;

		//d = g_strconcat("D3 ", c, "\n", NULL);
		rel_write_almost_all(conn, "D3 \n");
		//g_free(d);

		if (ctest){
			int i3;
			gs = g_string_sized_new(150);
			zg_string_eprintfa("e", gs, "P3 %s;%s;", gnet->myid, aband->operator_);
			for (i3 = 0; i3 < ctest->bands->len; i3++){
				struct band *b = (struct band *)g_ptr_array_index(ctest->bands, i3);
				if (b->readonly) continue;
				g_string_append_c(gs, toupper(b->bandchar));
			}
			if (ctest){
				zg_string_eprintfa("e", gs, ";%s;%s;%s\n", ctest->cdate, ctest->pcall, ctest->tname);
			}else{
				g_string_append(gs, ";;;");
			}

			g_string_append_c(gs, '\n');
			rel_write(conn, gs->str);
			g_string_free(gs, TRUE);
		}
	}
	if (strncmp(line, "P3", 2) == 0){ // peer info v3
		char *d;
        if (strlen(line) < 4) return;
        c = line + 3;

		d = g_strconcat("P3 ", c, "\n", NULL);
		rel_write_almost_all(conn, d);
		g_free(d);

		if (gnet->peers3){
			if (gnet->peers3->len > 0) g_string_append_c(gnet->peers3, ';');
			zg_string_eprintfa("e", gnet->peers3, "%s", c);
			//log_adds(gnet->peers3->str);
		}
	}
#ifdef VOIP
	if (strncmp(line, "VS", 2) == 0){ // voip spy
		int tok = 0;
		char *dstid, *d;

        if (strlen(line) < 4) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
#ifdef HAVE_SNDFILE
            char *id = z_tokenize(c, &tok);
            char *voipaddr = z_tokenize(c, &tok);
            log_addf("VS: id='%s'  voipaddr='%s'", id, voipaddr);
            voip_spy_remove(gssbd->voip, id);
            voip_spy_add(gssbd->voip, id, voipaddr);
            if (gssbd->voip){
                d = g_strdup_printf("VA %s;%s;%s:%d\n", id, gnet->myid, inet_ntoa(gnet->my.sin_addr), gssbd->voip->udpport);
                rel_write(conn, d);
                g_free(d);
            }
            voip_spy_dump(gssbd->voip);
#endif
		}else{
			d = g_strconcat("VS ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "EV", 2) == 0){ // end of voip spy
		int tok = 0;
		char *dstid, *d;

        if (strlen(line) < 4) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
#ifdef HAVE_SNDFILE
            char *id = z_tokenize(c, &tok);
            voip_spy_remove(gssbd->voip, id);
            voip_spy_dump(gssbd->voip);
#endif
		}else{
			d = g_strconcat("EV ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
	if (strncmp(line, "VA", 2) == 0){ // voip addr
		int tok = 0;
		char *dstid, *d;

        if (strlen(line) < 4) return;
		c = g_strdup(line + 3);
		dstid = z_tokenize(c, &tok);

		if (strcmp(dstid, gnet->myid) == 0){
#ifdef HAVE_SNDFILE
            char *id = z_tokenize(c, &tok);
            char *voipaddr = z_tokenize(c, &tok);
            log_addf("VA: id='%s'  voipaddr='%s'", id, voipaddr);
            voip_spy_update(gssbd->voip, id, voipaddr);
            voip_spy_dump(gssbd->voip);
#endif
		}else{
			d = g_strconcat("VA ", line + 3, "\n", NULL);
			rel_write_almost_all(conn, d);
			g_free(d);
		}
		g_free(c);
		return;
	}
#endif

    if (strncmp(line, "DO", 2)==0){ /* peers discovery with operators*/
        int i;
        GString *gs;
        struct conn *tmpconn;
        char *op;

        /*dbg("DI recvd\n");*/
        gs =g_string_sized_new(100);
        g_string_append(gs,"PO ");

        /* LOOK ALSO menu.c menu_spy_from_peer */

        op="---";
        if (ctest && aband && conn->operator_) op=aband->operator_;
        g_string_append_printf(gs, "%s;%s;", gnet->myid,op);

        for (i=0;i<gnet->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(gnet->peers,i);

            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_id) return;
            op="---";
            if (ctest && aband && tmpconn->operator_) op=tmpconn->operator_;
            g_string_append_printf(gs, "%s;%s;", tmpconn->remote_id, op);

        }
        g_string_append_printf(gs,"\n");
        trace(cfg->trace_sock, "sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }

    if (strncmp(line, "PO", 2)==0){ /* peers reply with operators*/
         /* TODO security - only if required */

        if (strlen(line)<4) return;
        c = line+3;

        trace(cfg->trace_sock, "PEers&OPs '%s'\n", c);
        CONDGFREE(gnet->allpeers);
        gnet->allpeers=g_strdup(c);

        if (!gnet->peerfunc) return;
        do_peer_operators_menu((void (*)(void *arg)) gnet->peerfunc);
        gnet->peerfunc=NULL;
        return;
    }

    if (strncmp(line, "DR", 2)==0){ /* peers discovery with operators and read-write bands */
        int i, j;
        GString *gs;
        struct conn *tmpconn;
        char *op;
        struct band *b;
        char rwbands[40], *rwb;

        /*dbg("DI recvd\n");*/
        gs =g_string_sized_new(100);
        g_string_append(gs,"PR ");

        /* LOOK ALSO menu.c menu_load_from_peer */

        op="---";
        if (ctest && aband && conn->operator_) op=aband->operator_;
        strcpy(rwbands, "");
        j=0;
        if (ctest){
            for (i=0; i<ctest->bands->len; i++){
                b=(struct band *) g_ptr_array_index(ctest->bands, i);
                if (b->readonly) continue;
                rwbands[j++]=z_char_uc(b->bandchar);
            }
            rwbands[j]='\0';
        }
        g_string_append_printf(gs, "%s;%s;%s;", gnet->myid,op, rwbands);

        for (i=0;i<gnet->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(gnet->peers,i);

            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_id) return;
            op="---";
            if (ctest && aband && tmpconn->operator_) op=tmpconn->operator_;
            rwb="";
            if (tmpconn->rwbands) rwb=tmpconn->rwbands;

            g_string_append_printf(gs, "%s;%s;%s;", tmpconn->remote_id, op, rwb);

        }
        g_string_append_printf(gs,"\n");
        trace(cfg->trace_sock, "sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }

    if (strncmp(line, "PR", 2)==0){ /* peers reply with operators and read-write bands */
         /* TODO security - only if required */

        if (strlen(line)<4) return;
        c = line+3;

        trace(cfg->trace_sock, "PEers&OPs&Bands '%s'\n", c);
        CONDGFREE(gnet->rwbpeers);
        gnet->rwbpeers=g_strdup(c);

        if (!gnet->peerfunc) return;
        do_spy_peer_menu((void (*)(void *arg)) gnet->peerfunc);
        gnet->peerfunc=NULL;
        return;
    }

    if (strncmp(line, "DC", 2)==0){ /* peers discovery with contest properties*/
        int i;
        GString *gs;
        struct conn *tmpconn;
        char *cdate, *pcall, *tname;
        struct zstring *zs;

        dbg("DC recvd\n");
        gs =g_string_sized_new(100);
        g_string_append(gs,"PC ");

        /* LOOK ALSO menu.c menu_contest_config_from_peer */
        if (ctest)
            g_string_append_printf(gs,"%s;%s %s %s;", gnet->myid, ctest->cdate, ctest->pcall, ctest->tname);
        for (i=0;i<gnet->peers->len;i++){
            tmpconn = (struct conn *)g_ptr_array_index(gnet->peers,i);

            if (!conn_prod_state(tmpconn)) continue;
            if (!tmpconn->remote_ac) continue;
            zs = zstrdup(tmpconn->remote_ac);
            cdate = ztokenize(zs, 1);
            pcall = ztokenize(zs, 0);
            tname = ztokenize(zs, 0);
            if (tname && *tname)
                g_string_append_printf(gs,"%s;%s %s %s;", tmpconn->remote_id, cdate, pcall, tname);
            zfree(zs);

        }
        g_string_append_printf(gs,"\n");
        dbg("sending '%s'\n", gs->str);
        rel_write(conn, gs->str);
        g_string_free(gs,TRUE);
        return;
    }

    if (strncmp(line, "PC", 2)==0){ /* peers reply with contest properties */
         /* TODO security - only if required */

        if (strlen(line)<4) return;
        c = line+3;

        dbg("PC recvd '%s' func=%p\n", c, gnet->peerfunc);
        CONDGFREE(gnet->allpeers);
        gnet->allpeers=g_strdup(c);

        if (!gnet->peerfunc) return;
        do_peer_operators_menu((void (*)(void *arg)) gnet->peerfunc);
        gnet->peerfunc=NULL;
        return;
    }


    if (strncmp(line, "RT", 2)==0){ /* request all tmpqso states*/
        gchar *d;
        struct band *b;

        if (!conn->is_same_ctest) return;

        if (strlen(line)<4) return;
        c=line+3;

        items=g_strsplit(c, ";", 2);
        if (!items[0] || !items[1]) goto x_rt;

		if (strcmp(items[0], "*") == 0){
			b = aband;
		}else{
			b = find_band_by_pband(items[0]);
			if (!b) {
				trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
				goto x_rt;
			}
		}
        if (strcmp(items[1], gnet->myid)==0) {
			wkd_send_all(b);
            goto x_rt;  /* noone has same myid, don't need to route */
        }

        d=g_strconcat("RT ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
x_rt:
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "OP", 2)==0){ /* operator */
        gchar *d;

        if (strlen(line)<4) return;
        c=line+3;

        CONDGFREE(conn->operator_);
        conn->operator_=g_strdup(c);

        d=g_strconcat("OP ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

    }

    if (strncmp(line, "RW", 2)==0){ /* read-write bands */
        gchar *d;

        if (strlen(line)<4) return;
        c=line+3;

        CONDGFREE(conn->rwbands);
        conn->rwbands=g_strdup(c);

        d=g_strconcat("RW ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

    }

    if (strncmp(line, "BA", 2)==0){ /* contest band settings */
        struct zstring *zs;
        struct band *b;
        char *c;

        if (strlen(line)<4) return;
        zs = zstrdup(line+3);
        dbg("zs->str='%s'\n", zs->str);
        dbg("ztokens=%d\n", ztokens(zs));

        if (ztokens(zs)<10){
            dbg("bad format '%s'\n", line+3);
            return;
        }

        c = ztokenize(zs, 1);
        b = find_band_by_pband(c);
        if (!b){
            dbg("Unknown band '%s'\n", c);
            zfree(zs);
            return;
        }
        c = ztokenize(zs, 0);
        b->psect = atoi(c);

        CONDGFREE(b->stxeq);
        b->stxeq = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->spowe);
        b->spowe = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->srxeq);
        b->srxeq = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->sante);
        b->sante = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->santh);
        b->santh = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->mope1);
        b->mope1 = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->mope2);
        b->mope2 = g_strdup(ztokenize(zs, 0));

        CONDGFREE(b->remarks);
        b->remarks = g_strdup(ztokenize(zs, 0));

        zfree(zs);

        b->dirty_save = 1;
        check_autosave();
    }

    if (strncmp(line, "RS", 2)==0){ /* rotar set */
        gchar *d;
        struct zstring *zs = NULL;
        gchar *netid, *rotstr, *qtf, *elev;

        //dbg("line='%s'\n", line);
        if (strlen(line)<4) return;
        c=line+3;

        if (!gnet->myid) return;

        zs = zstrdup(c);
        netid = ztokenize(zs, 1);
        if (!netid) goto x_rs;
        rotstr = ztokenize(zs, 0);
        if (!rotstr) goto x_rs;
        qtf = ztokenize(zs, 0);
        if (!qtf) goto x_rs;
        elev = ztokenize(zs, 0);
        if (!elev) goto x_rs;


        if (strcmp(netid, gnet->myid)==0){   // my rotar
            struct rotar *rot;
            rot = get_rotar_by_rotstr(rotstr);
            if (!rot) goto x_rs;
            dbg("rotstr='%s' rotchar=%c\n",rotstr, rot->rotchar);
            rot_seek(rot, atoi(qtf), atoi(elev));
        }

        d=g_strconcat("RS ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);
x_rs:;
        zfree(zs);
    }

    if (strncmp(line, "RU", 2)==0){ /* rotar update */
        gchar *d;

        //dbg("line='%s'\n", line);
        if (strlen(line)<4) return;
        c=line+3;

        rotar_remote_update(c);

        d=g_strconcat("RU ",c,"\n",NULL);
        rel_write_almost_all(conn, d);
        g_free(d);

    }

	if (strncmp(line, "RP", 2) == 0){ /* rotar stop */
		gchar *d;
		struct zstring *zs = NULL;
		gchar *netid, *rotstr;

		//dbg("line='%s'\n", line);
		if (strlen(line)<4) return;
		c = line + 3;

		if (!gnet->myid) return;

		zs = zstrdup(c);
		netid = ztokenize(zs, 1);
		if (!netid) goto x_rp;
		rotstr = ztokenize(zs, 0);
		if (!rotstr) goto x_rp;

		if (strcmp(netid, gnet->myid) == 0){   // my rotar
			struct rotar *rot;
			rot = get_rotar_by_rotstr(rotstr);
			if (!rot) goto x_rp;
			dbg("rotstr='%s' rotchar=%c\n", rotstr, rot->rotchar);
			rotar_stop(rot);
		}

		d = g_strconcat("RP ", c, "\n", NULL);
		rel_write_almost_all(conn, d);
		g_free(d);
	x_rp:;
		zfree(zs);
	}


    if (strncmp(line, "RC", 2)==0){ /* request of contest */
        gchar *d;
        GString *gs;
        struct zstring *zs, *zs2;
        char *bs[26+1];
        int i;

        if (strlen(line)<4) return;
        c = line+3;


        items=g_strsplit(c, ";", 0);
        if (net_route(line,items)) return;
        if (!items[0] || !items[1]) return; /* dst,src */
        if (!ctest) return;

        for (i=0; i<ctest->bands->len; i++){
            struct band *b;

            if (i > 26) return;
            b = (struct band*)g_ptr_array_index(ctest->bands, i);
            zs = band_format(b);
            bs[i] = g_strdup(zs->str);
            zfree(zs);
        }
        bs[i] = NULL;

        zs = contest_format(ctest);
        zs2 = zconcatesc(zs->str, bs[0], bs[1], bs[2], bs[3],
                bs[4], bs[5], bs[6], bs[7],
                bs[8], bs[9], bs[10], bs[11],
                bs[12], bs[13], bs[14], bs[15],
                bs[16], bs[17], bs[18], bs[19],
                bs[20], bs[21], bs[22], bs[23],
                bs[24], bs[25], NULL);
        for (i=0; bs[i]!=NULL; i++) g_free(bs[i]);
        zfree(zs);


        gs=g_string_sized_new(10000);

        d=g_strdup_printf("SC %s\n",zs2->str);
        zfree(zs2);
        rel_write(conn,d);

        g_free(d);
        g_string_free(gs,TRUE);
        g_strfreev(items);
        return;
    }

    if (strncmp(line, "SC", 2)==0){ /* configuration line */
        struct zstring *zs2;

        if (ctest) return;  // contest already opened

        if (strlen(line)<4) return;
        c = line+3;

       // dbg("recvd SC %s\n", c);

        zs2 = zstrdup(c);
        init_ctest2(0, 1, zs2, NULL);
        zfree(zs2);

#if 0
        items=g_strsplit(c, ";", 2);
        if (net_route(line,items)) goto x_cf;

        if (!items[0] || !items[1] || !items[2]) goto x_cf; /* dst,src */


        d = strchr(items[2],'#');
        if (d) *d='\0';
        g_strstrip(items[2]);
        if (strlen(items[2])==0) goto x_cf;

        if (strcmp(items[2],"eof")==0){
            log_addf(VTEXT(T_LOADED_CFG_SSSS),cfg->pcall, cfg->pwwlo, cfg->padr1,cfg->padr2);
            goto x_cf;
        }

        d = g_strconcat(items[2],"\n",NULL);
/*        dbg("doing something with '%s'\n",items[2]);*/
        read_rc_line(d);
        g_free(d);
x_cf:;
        g_strfreev(items);
#endif
        return;
    }

	if (strncmp(line, "PH", 2) == 0){ /* contest phase */
		if (!ctest) return;
		if (!conn->is_same_ctest) return;

		if (strlen(line)<4) return;
		c = line + 3;

		ctest->phase = atoi(c);

		char *d = g_strdup_printf("PH %d\n", ctest->phase);
		rel_write_almost_all(conn, d);
		g_free(d);
		return;
	}


}


gchar *qso_to_string(struct qso *q){
    gchar *c;

	c = g_strdup_printf("QS %s;%s;%s;%s;"
		"%d;%s;%s;%s;"
		"%s;%s;%s;%s;"
		"%s;%d;%s;%s;"
		"%d;%s;%1.0f;%d\n",
            q->band->pband, q->date_str, q->time_str, q->error?"ERROR":q->callsign,
            q->mode,        C0(q->rsts), C0(q->qsonrs),   C0(q->rstr),
            C0(q->qsonrr),  C0(q->exc),  C0(q->locator),  q->source,
            q->operator_,   (int)q->stamp,    q->callsign, "",
            q->ser_id, fixsemi(C0(q->remark)), q->qrg, q->phase);
    return c;
}

void compare_remote_with_me(gpointer key, gpointer value, gpointer data){
    gchar *source, *rem_latest_str;
    ZPtrArray *ia;
    GHashTable *remote_latests;
    struct qso dummyq, *pdq=&dummyq, *q;
    int i;
    gchar *c;
    gint first_qso;

    source = (gchar*) key;
    ia = (ZPtrArray *) value;
    remote_latests = (GHashTable *)data;

    /*dbg("nsource=%s, ia->len=%d\n", source, ia->len);*/

    rem_latest_str = (gchar *) g_hash_table_lookup(remote_latests, source);

    if (rem_latest_str){
       /* dbg("remote has some qso from this source\n");*/
        memset(&dummyq, 0, sizeof(dummyq));
        dummyq.stamp = atoi(rem_latest_str);
        first_qso = z_ptr_array_bsearch_index(ia, &pdq, compare_stamp);

        if (first_qso<0) { /* remote's last qso not found in my qsos */
            /*dbg("remote's last qso not found between my qsos");*/
            q = (struct qso *) z_ptr_array_index(ia, ia->len-1); /* my last qso (ia->len!=0) */
            if (compare_stamp(&q, &pdq)<0){
                trace(cfg->trace_qsos, "  %s: remote has newer qso than me",source);
            }else{
                trace(cfg->trace_qsos, "  %s: ooops remote's last not found but is older!",source);
                first_qso = 0; /* Ooops! remote's last qso is missing */
            }
        }

    /*    dbg("nsource=%s, first_qso=%d ia->len=%d\n", source, first_qso, ia->len);*/

        /* there can be some qsos with the same stamp before */
        /* these must be replicated too */
        /*dbg("first_qso(1)=%d\n", first_qso);*/
        while(first_qso>0){
            q = (struct qso *) z_ptr_array_index(ia, first_qso-1);
            if (compare_stamp(&q, &pdq)!=0) break;
            first_qso--;
        }
    }else{ /* remote has no qso from this source */
        trace(cfg->trace_qsos, "  %s: remote has no qso\n", source);
        first_qso = 0;
    }

    dbg("replicating sock=%d source=%22s ser_id %d...%d \n", sconn->sock, source, first_qso, ia->len-1);

    for (i=first_qso; i<ia->len; i++){
        q = (struct qso *) z_ptr_array_index(ia, i);
        if (!q) continue;
        c = qso_to_string(q);
       /* dbg("replicating %s %s %d %s/%d\n",q->callsign, q->qsonrs, q->stamp, source,q->ser_id);*/
        rel_write(sconn, c);

        if (strlen(c)>0 && c[strlen(c)-1]=='\n') c[strlen(c)]='\0';
        dump_qso(q,"replicating2\n");

        g_free(c);
    }
    trace(cfg->trace_qsos, "----end of replications\n");


}

void qso_from_net(struct conn *conn, gchar *str){
    int i;
    gchar **items;
    struct band *b;
    struct qso *lastqso, *eq, *q;
    gint last_qsonr, mode, qsonr, stamp, ser_id;
    gchar *date_str, *time_str, *callsign_or_error, *rsts, *qsonrs;
    gchar *rstr, *qsonrr, *exc, *locator, *source, *operator_, *callsign, *remark;
    int found,sortit;
    int same_km=0;
    double qrg = 0.0;
	int phase = 1;
    char rawcall[20];


    items=g_strsplit(str, ";", 0);
    for (i=0;i<=16;i++) if (items[i]==NULL) {g_strfreev(items);return;}

    date_str=items[1]; time_str=items[2];  callsign_or_error=items[3];
    mode=atoi(items[4]); rsts=items[5];     qsonrs=items[6];    rstr=items[7];
    qsonrr=items[8];     exc=items[9];      locator=items[10];  source=items[11];
    operator_=items[12];  stamp=atoi(items[13]); callsign=items[14];
    ser_id=atoi(items[16]);

    if (!items[17]){
        remark="";
    }else{
        remark=items[17];

        if (items[18]){
            qrg = atof(items[18]);
			if (items[19]){
				phase = atoi(items[19]);
			}
        }
    }

   /* dbg("qso_from_net '%s'\n", str);*/
    /*dbg("qso_from_net sock=%d %d %s %-10s %s | ", */
            /*conn->sock, stamp, qsonrs, callsign, source);*/

    trace(cfg->trace_qsos, "\n----qso_from_net: '%s'", str);

    b = find_band_by_pband(items[0]);
    if (!b) {
        trace(cfg->trace_qsos, "!!! unknown band '%s'", items[0]);
        goto x;
    }

    trace(cfg->trace_qsos, "from %s '%s'\n",conn->remote_id, str);

    z_get_raw_call(rawcall, callsign);
    q = (struct qso *)g_hash_table_lookup(b->rawqsoshash, rawcall);
    if (q != NULL) g_hash_table_remove(b->rawqsoshash, rawcall);


    qsonr = atoi(qsonrs);
    q = get_qso_by_id(b, source, ser_id);
  //          if (q) { dbg(" ID ");trace(cfg->trace_qsos, "  found by ID (%s:%d)", source, ser_id);dump_qso(q,"id");}
    if (!q && ctest->qsoused>0 && !ctest->qsoglob){
        q = get_qso_by_qsonr(b, qsonr);
        if (q) { trace(cfg->trace_qsos, "  found by QSO NR");dump_qso(q,"nr");}
    }
    found=1;
    if (!q) {
        found=0;
        trace(cfg->trace_qsos, "  QSO not found, creating new");
        if (ctest->qsoused>0 && !ctest->qsoglob){  /* fill with errors */
            lastqso = get_qso(b, b->qsos->len-1); /* -1 is correct */
            if (lastqso){
                last_qsonr = atoi(lastqso->qsonrs);
                dump_qso(lastqso,"lastqso");
            }else
                last_qsonr = 0;
            if (last_qsonr+1 != qsonr) trace(cfg->trace_qsos, "  fill with ERRORS QSONRs <%d..%d)\n", last_qsonr+1, qsonr);
            for (i=last_qsonr+1; i<qsonr; i++){
                dbg("!!! missing qso nr %d, adding error\n", i);
                eq = g_new0(struct qso, 1);
                eq->source    = g_strdup("neterr"); /* ignored by add_qso_to_index */
                eq->operator_ = g_strdup("net");
                eq->date_str  = g_strdup("");
                eq->time_str  = g_strdup("");
                eq->callsign  = g_strdup("ERROR");
                eq->rsts      = g_strdup("");
                eq->rstr      = g_strdup("");
                eq->qsonrs    = g_strdup_printf("%03d", i);
                eq->qsonrr    = g_strdup("");
                eq->exc       = g_strdup("");
                eq->locator   = g_strdup("");
                eq->remark    = g_strdup("");
                eq->qrg       = 0.0;
				eq->phase     = phase;
                eq->error     = 1;
                add_qso(b, eq);
                update_stats(b, b->stats, eq);
                recalc_allb_stats();
                minute_stats(b);
                dump_qso(eq, "error");
            }
            if (!some_replicating(gnet)) check_autosave();
        }
        q = g_new0(struct qso, 1);
      /*  dbg("N ");*/
    }else{
		struct qso *q2;
		char rawcall2[20];

        trace(cfg->trace_qsos, "  updating existing qso");
        dump_qso(q,"updating");

		z_get_raw_call(rawcall2, q->callsign);
	    q2 = (struct qso *)g_hash_table_lookup(b->rawqsoshash, rawcall2);
	    if (q2 != NULL) g_hash_table_remove(b->rawqsoshash, rawcall2);

    }
    g_hash_table_insert(b->rawqsoshash, g_strdup(rawcall), q);
   /* dbg("F%d S%d ", found, stamp-q->stamp);*/

    if (stamp < q->stamp) {
     /*   dbg("stamp < q->stamp\n");*/
        goto add;
    }
    sortit = (stamp != q->stamp);

    q->band = b;
    STORE_STR   (q,date_str);
    STORE_STR   (q,time_str);

    if (q->locator){
        same_km=!strcasecmp(q->locator, locator);
    }else{
        same_km=2; /*new qso or contest without locator*/
    }

    if (strcasecmp(callsign_or_error, "ERROR")==0){
        if (!q->error) same_km=0;
        q->error = 1;
    }else{
        if (q->error) same_km=0;
        q->error=0;
    }


    STORE_INT   (q,mode);
    STORE_STR   (q,rsts);
    STORE_STR   (q,qsonrs);
    STORE_STR   (q,rstr);
    STORE_STR   (q,qsonrr);
    STORE_STR   (q,exc);
    STORE_STR_UC(q,locator);
    STORE_STR   (q,remark);
    fixsemi(q->remark);
    STORE_INT   (q,ser_id);
    compute_qrbqtf(q);

    if (q && q->source && strcmp(q->source,"neterr")==0){
        /* spojeni bylo predtim sitova chyba, musime prepsat zdroj */
        STORE_STR(q,source);
        add_qso_to_index(q,0);
        if (!q->error) invalidate_tmpqso(b, q);
        sortit=1;
    }else{
        /* updatuje se existujici qso */
        dbg("--------- found=%d\n", found);
   /*     if (!found)  STORE_STR(q,source); */

        if (q && q->source && strcmp(source, q->source)!=0){
            dbg("--------- bude se prepisovat source %s -> %s\n", q->source, source);
            dump_all_sources(ctest);
            dbg("---before remove_qso_from_index\n");
            dump_all_sources(ctest);
            remove_qso_from_index(q);
            dbg("---after remove_qso_from_index\n");
            dump_all_sources(ctest);

            STORE_STR(q, source);
            dbg("---before add_qso_to_index\n");
            dump_all_sources(ctest);
            add_qso_to_index(q, 1);
            dbg("---after add--\n");
            dump_all_sources(ctest);
        }else{
            STORE_STR(q, source);
        }


    }

    STORE_STR_UC(q,operator_);
    STORE_INT   (q,stamp);
    STORE_STR_UC(q,callsign);
    /* items[15] unused (century) */
    STORE_INT(q, qrg);  /* qrg is double but macro is the same */
	STORE_INT(q, phase);

    if (sortit) {
        g_hash_table_foreach(ctest->bystamp, foreach_source_qsort_by_stamp, NULL);
        z_ptr_array_qsort(ctest->allqsos, compare_date_time_qsonrs);
    }
/*update_stats(b->stats, b, q);*/

    write_qso_to_swap(b, q);

    if (!gses->redraw_timer_id)
        gses->redraw_timer_id = zselect_timer_new(zsel, DELAY_AFTER_REPLICATION, net_timer_redraw, NULL);

add:;
    dirty_band(b);
    if (!found){
/*      dbg("adding\n");*/
        add_qso(b, q);
        if (!q->error) invalidate_tmpqso(b, q);
        update_stats(b, b->stats, q);
        recalc_allb_stats();
        minute_stats(b);
#ifdef Z_HAVE_SDL
        if (b==aband) map_add_qso(q, aband);   /*plot new QSO only if it is on active band*/
#endif
    }else{
#ifdef Z_HAVE_SDL
        if (!same_km) map_clear_qso(q, aband);
#endif
/*      dbg("dont adding\n");*/
    }
    replicate_qso(conn, q);
    if (!some_replicating(gnet)) check_autosave();
    dump_qso(q,"after");
x:; /* only unknown band */
    g_strfreev(items);
    trace(cfg->trace_qsos, "\n");
}

/*****************************************************************/
void net_send_id(){
    gchar *c;

    c = g_strdup_printf("ID %s:%d\n",
            inet_ntoa(gnet->my.sin_addr),
            ntohs(gnet->my.sin_port));
    rel_write_all(c);
    g_free(c);
}


void net_send_ac(){
    gchar *c;
    int i,j;
    struct conn *conn;
    char qrvbands[50];

    if (!ctest) return;

    for (i=j=0;i<ctest->bands->len;i++){
        struct band *b;
        b=(struct band *)g_ptr_array_index(ctest->bands, i);
        qrvbands[j++]=z_char_uc(b->bandchar);
    }
    qrvbands[j]='\0';


    c = g_strdup_printf("AC %s;%s;%s;%s\n",
            ctest->cdate?ctest->cdate:"",
            ctest->pcall?ctest->pcall:"",
            ctest->tname?ctest->tname:"",
            qrvbands);
    rel_write_all(c);
    g_free(c);

    if (gnet->master){
        net_test_same_contest(gnet->master, gnet->master->remote_ac);
    }
    if (gnet->remote){
        net_test_same_contest(gnet->remote, gnet->remote->remote_ac);
    }

    for (i=0; i<gnet->peers->len; i++){
        conn = (struct conn *) g_ptr_array_index(gnet->peers, i);
        net_test_same_contest(conn, conn->remote_ac);
    }
}

void net_send_operator(void){
    int i;
    char *c;
	char *op = cfg->pcall;

	if (cfg->operator_ && *cfg->operator_) op = cfg->operator_;
    c = g_strdup_printf("OP %s\n", op);
    rel_write_all(c);
    g_free(c);

	if (ctest){
		for (i=0;i<ctest->bands->len;i++){
			struct band *b;
			b=(struct band *)g_ptr_array_index(ctest->bands, i);
			wkd_tmpqso(b, WT_OPERATOR, b->operator_);
		}
	}
}

void net_send_read_write_bands(void){
    int i, j;
    char rwbands[40];
    gchar *c;

    strcpy(rwbands, "");
    j=0;
    if (ctest){
        for (i=0;i<ctest->bands->len;i++){
            struct band *b;
            b=(struct band *)g_ptr_array_index(ctest->bands, i);
            if (b->readonly) continue;
            rwbands[j++]=z_char_uc(b->bandchar);
        }
        rwbands[j]='\0';
    }
    c=g_strdup_printf("RW %s\n", rwbands);
    rel_write_all(c);
    g_free(c);
}

void net_send_talk(void){
    int i;
    gchar *c;
		
    /*if (gnet->master){
        rel_write_all("TB master1\n");
        rel_write_all("TB master2\n");
    }else{
        rel_write_all("TB slave1\n");
        rel_write_all("TB slave2\n");
    }
    return;	*/

	for (i = Z_MAX((int)gtalk->items->len - 20, 0); i < gtalk->items->len; i++){ // latest 20 items
		char *talk = (char*)g_ptr_array_index(gtalk->items, i);

		if (strlen(talk) < 7) continue;
		if (strncmp(talk + 6, "***", 3) == 0) continue;

		c=g_strdup_printf("TB %s\n", talk);
		rel_write_all(c);
	    g_free(c);
	}
}

void net_test_same_contest(struct conn *conn, gchar *ac_text){
    gchar **items;
    gchar *cdate, *pcall, *tname, *qrvb;
    int i,j;
    char qrvbands[50];

/*    log_addf("%d test_same", conn->sock);*/
    if (!ctest || !ac_text) return;
	if (!conn_prod_state(conn)) return;


    for (i=j=0;i<ctest->bands->len;i++){
        struct band *b;
        b=(struct band *)g_ptr_array_index(ctest->bands, i);
        qrvbands[j++]=z_char_uc(b->bandchar);
    }
    qrvbands[j]='\0';

    items=g_strsplit(ac_text, ";", 0);
    for (i=0;i<4;i++) {
        if (items[i]==NULL) {
            log_adds(VTEXT(T_CTEST_NOT_SAME_AC));
            g_strfreev(items);
            return;
        }
    }
    cdate = items[0];
    pcall = items[1];
    tname = items[2];
    qrvb  = items[3];

    if (strncmp(cdate, ctest->cdate, 8)==0 &&
        strcasecmp(pcall, ctest->pcall)==0 &&
        strcasecmp(tname, ctest->tname)==0 &&
        strcasecmp(qrvb, qrvbands)==0){

        gchar *latests_str;

        /*dbg(" To je on!! Meho srdce sampion!! sock=%d\n", conn->sock);*/

        latests_str = get_latests_str();
        rel_write(conn, latests_str);
        g_free(latests_str);
        //log_addf("Contest is the same");
        conn->is_same_ctest = 1;
    }else{
        /*GString *gs;
        gs=g_string_new("Contest is not the same: ");
        if (strncmp(cdate, ctest->cdate, 8)) g_string_append_printf(gs, "DATE(%s,%s) ", cdate, ctest->cdate);
        if (strcasecmp(pcall, ctest->pcall)) g_string_append_printf(gs, "CALL(%s,%s) ", pcall, ctest->pcall);
        if (strcasecmp(tname, ctest->tname)) g_string_append_printf(gs, "CONTEST(%s,%s) ", tname, ctest->tname);
        if (strcasecmp(qrvb, qrvbands))      g_string_append_printf(gs, "BANDS(%s,%s) ", qrvb, qrvbands);
        log_addf(gs->str);
        g_string_free(gs, 1);*/
        conn->is_same_ctest = 0;
    }
    g_strfreev(items);
	trace(cfg->trace_recv, "%s: is_same_ctest=%d\n", conn->fid, conn->is_same_ctest);
}


void net_timer_redraw(void *arg){
    struct band *b;
    int i;

    if (ctest && !ctest->qsoglob){
        for (i=0; i<ctest->bands->len; i++){
            b = (struct band *)g_ptr_array_index(ctest->bands, i);
            recalc_stats(b);
            recalc_worked_skeds(b);

            if (ctest->qsoused && b->qsos->len+1 != atoi(b->tmpqsos[0].qsonrs)){
                g_free(b->tmpqsos[0].qsonrs);
                b->tmpqsos[0].qsonrs = g_strdup_printf("%03d", b->qsos->len+1);
                wkd_tmpqso(b, WT_QSONRS, b->tmpqsos[0].qsonrs);
            }
        }
    }

    redraw_later();
    gses->redraw_timer_id = 0;
}

void replicate_qso(struct conn *sourceconn, struct qso *q){
    gchar *c;
    int i;
    struct conn *conn;

    c = qso_to_string(q);
    trace(cfg->trace_qsos, "replicating1: %s(%s:%d) band=%c", q->callsign, q->source, q->ser_id, z_char_uc(q->band->bandchar));
        dump_qso(q,"replicating1");

    if (gnet->master &&
        conn_prod_state(gnet->master) &&
        (sourceconn==NULL || sourceconn->sock!=gnet->master->sock) ){

        /*dbg("replicating mast=%d %d %s %s\n", gnet->master->sock, q->stamp, q->qsonrs, q->callsign);*/
        rel_write(gnet->master, c);
    }

    if (gnet->remote &&
        conn_prod_state(gnet->remote) &&
        (sourceconn==NULL || sourceconn->sock!=gnet->remote->sock) ){

        /*dbg("replicating mast=%d %d %s %s\n", gnet->master->sock, q->stamp, q->qsonrs, q->callsign);*/
        rel_write(gnet->remote, c);
    }

    for (i=0; i<gnet->peers->len; i++){
        conn = (struct conn *)g_ptr_array_index(gnet->peers, i);

        if (conn_prod_state(conn) &&
            (sourceconn==NULL || sourceconn->sock!=conn->sock)) {

           /* dbg("replicating sock=%d %d %s %s\n", conn->sock, q->stamp, q->qsonrs, q->callsign);*/
            rel_write(conn, c);
        }
    }

    g_free(c);

}


gchar *get_timer_str(struct conn *conn){
    static char s[100];
    ztime interval;

    strcpy(s,"");
    if (!conn) return s;

    interval=zselect_timer_get(zsel, conn->timer);
    interval/=1000;

    sprintf(s, "%4d", (int)interval);

    return s;
}

void send_config_request(int no){
    char *peerid,*c;
    struct conn *gw;
    gchar **items;
    int i;

    /*ndbg("send_config_request '%d'\n",no);*/

    items = g_strsplit(gnet->allpeers,";",0);

    for (i=0; items[i]!=NULL;i++){
        if (i==no) goto found;
    }
    g_strfreev(items);
    return;

found:
    peerid=items[i];

    if (cmp_sin(&gnet->global, &gnet->my)==0) { /* i'm master */
        gw=find_conn_by_remote_id(peerid);
        if (!gw) {
            log_addf(VTEXT(T_PEER_NOT_FOUND), peerid);
            goto x;
        }
    }else{
        gw=gnet->master;
    }
    if (!conn_prod_state(gw)) goto x;
    /* gw - connection to requested peer/gateway to peer */

    c=g_strdup_printf("CR %s;%s\n",peerid,gnet->myid);
    rel_write(gw,c);
    g_free(c);

x:;

    g_strfreev(items);
}

void send_cwdb_request(int no){
    char *peerid,*c;
    struct conn *gw;
    gchar **items;
    int i;

    /*dbg("send_cwdb_request '%d'\n",no);*/

    items = g_strsplit(gnet->allpeers,";",0);

    for (i=0; items[i]!=NULL;i++){
        if (i==no) goto found;
    }
    g_strfreev(items);
    return;

found:
    peerid=items[i];

    if (cmp_sin(&gnet->global, &gnet->my)==0) { /* i'm master */
        gw=find_conn_by_remote_id(peerid);
        if (!gw) {
            log_addf(VTEXT(T_PEER_NOT_FOUND), peerid);
            goto x;
        }
    }else{
        gw=gnet->master;
    }
    if (!conn_prod_state(gw)) goto x;
    /* gw - connection to requested peer/gateway to peer */

    c=g_strdup_printf("WR %s;%s\n",peerid,gnet->myid);
    rel_write(gw,c);
    g_free(c);

x:;

    g_strfreev(items);
}


void send_contest_config_request(int no){
    char *peerid,*c;
    struct conn *gw;
    gchar **items;
    int i;

    dbg("send_contest_config_request(%d)\n", no);

    items = g_strsplit(gnet->allpeers,";",0);

    for (i=0; items[i]!=NULL;i++){
        if (i==no) goto found;
    }
    g_strfreev(items);
    return;

found:
    peerid=items[i];

    if (cmp_sin(&gnet->global, &gnet->my)==0) { /* i'm master */
        gw=find_conn_by_remote_id(peerid);
        if (!gw) {
            log_addf(VTEXT(T_PEER_NOT_FOUND), peerid);
            goto x;
        }
    }else{
        gw=gnet->master;
    }
    if (!conn_prod_state(gw)) goto x;
    /* gw - connection to requested peer/gateway to peer */

    c=g_strdup_printf("RC %s;%s\n",peerid,gnet->myid);
    rel_write(gw,c);
    g_free(c);

x:;

    g_strfreev(items);

}


int some_replicating(struct net *net){
    int i;
    struct conn *conn;

    if (gnet->master &&
        conn_prod_state(gnet->master) &&
        gnet->master->replicating) return 1;

	if (gnet->remote &&
        conn_prod_state(gnet->remote) &&
        gnet->remote->replicating) return 1;

    for (i=0;i<gnet->peers->len;i++){
        conn = (struct conn *) z_ptr_array_index(gnet->peers, i);
        if (!conn_prod_state(conn)) continue;
        if (conn->replicating) return 1;
    }
    return 0;
}


void iface_info(GString *gs){
    int i, mi;
    struct ziface_struct ifaces[MAX_INTERFACES];

    g_string_append_printf(gs, "\n  interfaces info:\n");
    mi = zifaces_get(ifaces, MAX_INTERFACES, 1); /* network byteorder */
    for (i=0;i<mi; i++){
        g_string_append_printf(gs, "interface %6s: ", ifaces[i].name);
        g_string_append_printf(gs, "%-15s ", inet_ntoa(ifaces[i].ip));
        g_string_append_printf(gs, "%s ", inet_ntoa(ifaces[i].netmask));
        g_string_append_printf(gs, "\n");
    }
    g_string_append_printf(gs, "\n");

}

void conn_set_fid(struct conn *conn){
//	int port = ntohs(conn->sin.sin_port);

	zg_free0(conn->fid);
	conn->fid = g_strdup_printf("%3d", ntohl(conn->sin.sin_addr.s_addr) & 0xff);
	/*if (port == NET_PORT) return;
	if (port > NET_PORT && port < NET_PORT + 'z' - 'a') {
		conn->fid[strlen(conn->fid) - 1] = 'a' + port - NET_PORT - 1;
		return;
	}
    conn->fid[strlen(conn->fid) - 1] = '!';*/
}
