/* AX25 server code */

#include <stdio.h>
#include <ctype.h>
#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "iface.h"
#include "timer.h"
#include "ax25.h"
#include "lapb.h"
#include "cmdparse.h"
#include "session.h"
#include "slip.h"
#include "tnc2.h"

#ifdef AX25
/* "start" and "stop" subcommands */
static int tnc_start(),axdigi_start(),mheard_start(),bridge_start(),
	   tnc2_start(),mbox_start();
static struct cmds axstartcmds[] = {
	"bridge",	bridge_start,	0, NULLCHAR,  "cannot start bridge",
#ifdef SMTPC
	"mbox",		mbox_start,	0, NULLCHAR,  "cannot start mbox",
#endif
#ifdef MHEARD
	"mheard",	mheard_start,	0, NULLCHAR,  "cannot start mheard",
#endif
	"netdigi",	axdigi_start,	0, NULLCHAR,  "cannot start netdigi",
	"tnc",		tnc_start,	0, NULLCHAR,  "cannot start tnc",
#ifdef TNC2
	"tnc2",		tnc2_start,	2, "[<dev>=]<iface> ...",
						      "cannot start tnc2",
#endif
	NULLCHAR,	NULLFP,		0,
		"?options", NULLCHAR
};
static int tnc_stop(),axdigi_stop(),mheard_stop(),bridge_stop(),
	   tnc2_stop(),mbox_stop();
static struct cmds axstopcmds[] = {
	"bridge",	bridge_stop,	0, NULLCHAR, NULLCHAR,
#ifdef SMTPC
	"mbox",		mbox_stop,	0, NULLCHAR, NULLCHAR,
#endif
#ifdef MHEARD
	"mheard",	mheard_stop,	0, NULLCHAR, NULLCHAR,
#endif
	"netdigi",	axdigi_stop,	0, NULLCHAR, NULLCHAR,
	"tnc",		tnc_stop,	0, NULLCHAR, NULLCHAR,
#ifdef TNC2
	"tnc2",		tnc2_stop,	0, NULLCHAR, "cannot stop tnc2",
#endif
	NULLCHAR,	NULLFP,		0,
		"?options", NULLCHAR
};

static void digi_a_state(),digi_b_state(),digi_recv(),digi_send();
static void mheard_state(),mheard_send();
static void bridge_recv(),bridge_state();
void close_ax25(),disc_ax25();

extern char nospace[];
extern char *ax25mesgs[];

/* start and stop command multiplexers */
doaxstart(argc,argv)
int argc;
char *argv[];
{
	return subcmd(axstartcmds,argc,argv);
}
doaxstop(argc,argv)
int argc;
char *argv[];
{
	return subcmd(axstopcmds,argc,argv);
}

/* start an ax25 server */

static
ax_sstart (port,r_upcall,t_upcall,s_upcall)
int port;
void (*r_upcall)(),(*t_upcall)(),(*s_upcall)();

{
	register struct ax25_call *axp;
	int found = 0;
	void ax_noserv();

	for(axp = ax25_port; axp != NULLAXCALL; axp = axp->p_next){
		if(axp->port == port && axp->s_upcall == ax_noserv){
			axp->r_upcall = r_upcall;
			axp->t_upcall = t_upcall;
			axp->s_upcall = s_upcall;
			found++;
		}
	}

	if(!found){
		printf("port number %d not found or busy\n",port);
		return 1;
	}

	return 0;
}

/* stop an ax25 server */

static
ax_sstop (port)
int port;

{
	register struct ax25_call *axp;
	void ax_noserv();

	for (axp = ax25_port; axp != NULLAXCALL; axp = axp->p_next){
		if(axp->port == port){
			axp->r_upcall = axp->t_upcall = NULLVFP;
			axp->s_upcall = ax_noserv;
		}
	}
}

char *tnc_conn_msg = NULLCHAR;		/* connect message for TNC */

/* Start up tnc server */
static
tnc_start(argc,argv)
int argc;
char *argv[];
{
	void tnc_state();
	char mesg[256];
	int i;

	if(argc > 1){			/* set-up a connect message */
	    mesg[0] = '\0';
	    for(i = 1; i < argc; i++){
		strcat(mesg,argv[i]);
		strcat(mesg,"\r");
	    }
	    if(tnc_conn_msg != NULLCHAR)
		free(tnc_conn_msg);
	    if((tnc_conn_msg = malloc(strlen(mesg)+2)) != NULLCHAR){
		tnc_conn_msg[0] = PID_FIRST|PID_LAST|PID_NO_L3;
		strcpy(tnc_conn_msg + 1,mesg);
	    }
	}
	return ax_sstart(TNCPORT,NULLVFP,NULLVFP,tnc_state);
}

/* Shut down tnc server */
static
tnc_stop()
{
	return ax_sstop(TNCPORT);
}

/* Handle new incoming terminal sessions
 * This is the default state-change upcall function, used when
 * someone else connects to us
 */
void
tnc_state(axp,old,new,msg)
register struct ax25_cb *axp;
int old,new,msg;

{
	struct session *s;
	char remote[100];
	long tloc;
	void ax_rx(),ax_tx(),ax_state();
	int ax_parse();

	if(new != CONNECTED || msg != LAPBCONN) /* only interested in new connections */
		return;
	if((s = newsession()) == NULLSESSION){
		/* Out of sessions, send DM */
		axp->state = DISCONNECTED;
		return;
	}
	pax25(remote,&axp->addr.dest);
	if ((s->name = malloc((int16)strlen(remote)+1)) == NULLCHAR){
		freesession(s);
		axp->state = DISCONNECTED;
		return;
	}
	s->type = AX25TNC;
	s->cb.ax25_cb = axp;
	s->parse = ax_parse;
	strcpy(s->name,remote);
	stop_timer(&axp->t4);		/* no T4 disconnects on TNC links */
	axp->t4.start = 0;
	axp->r_upcall = ax_rx;
	axp->t_upcall = ax_tx;
	axp->s_upcall = ax_state;
	axp->user = (char *)s;
	pdax25(remote,&axp->addr);	/* add digipeaters */
	time(&tloc);
	printf("\007%.15s Incoming AX25 session %lu from %s(%s)\n",
	    ctime(&tloc) + 4,(long) (s - sessions),remote,axp->interface->name);
	fflush(stdout);
	log_ax(axp,"Incoming AX25 session");
	if(tnc_conn_msg != NULLCHAR)		/* a connect message defined? */
	    enqueue(&axp->txq,qstring(tnc_conn_msg));
}

/* Start up netdigi server */
static
axdigi_start(argc,argv)
int argc;
char *argv[];
{
	return ax_sstart(NETDIGIPORT,NULLVFP,NULLVFP,digi_a_state);
}

/* Shut down netdigi server */
static
axdigi_stop()
{
	return ax_sstop(NETDIGIPORT);
}

static void
digi_a_state(axp,old,new,msg)
register struct ax25_cb *axp;
int old,new,msg;

{
	register struct ax25_cb *axpb;
	register int i,j;
	struct ax25_cb *find_ax25(),*cr_ax25();
	struct ax25_call *axport;
	struct ax25_addr usecall;
	struct ax25 addr;
	char mesg[128],tmp[10];
	extern int16 t1init,t2init;

	axpb = find_ax25(&axp->addr.source,&axp->addr.dest);
	switch (new)
	{
	case CONNECTED:
	    if(old != DISCONNECTED || msg != LAPBCONN)/* not a new connection? */
		return;				/* ignore state change */

	    if(axpb != NULLAX25) {		/* connection already exists */
		axp->state = DISCONNECTED;
		return;
	    }

	    /* New connection via NetDigi, setup response and 2nd link */
	    pax25(tmp,&((struct ax25_call *) axp->user)->addr);
	    sprintf(mesg,"%cNetDigi %s: Please wait for connection.\r",
			 PID_FIRST|PID_LAST|PID_NO_L3,tmp);
	    /* must not call send_ax25 because it would send out the
	       message even before the UA was transmitted */
	    enqueue(&axp->txq,qstring(mesg));

	    ASSIGN(usecall,((struct ax25_call *) axp->user)->addr);
	    for (axport = ax25_port; axport != NULLAXCALL; axport = axport->p_next){
		if(axport->port == NETDIGIPORT &&
		   axport->interface == axp->interface){
		    ASSIGN(usecall,axport->addr);
		}
	    }
	    usecall.ssid &= ~E;

	    /* Swap source and destination, reverse digi string */
	    ASSIGN(addr.source,axp->addr.dest);
	    ASSIGN(addr.dest,axp->addr.source);

	    /* Construct reverse digipeater path */
	    for(i=axp->addr.ndigis-1,j=0;i >= 0;i--,j++){
		ASSIGN(addr.digis[j],axp->addr.digis[i]);
		addr.digis[j].ssid &= ~(E|REPEATED);
	    }
	    addr.ndigis = axp->addr.ndigis;
	    /* set all repeated flags up to my callsign */
	    for(i=axp->addr.ndigis-1;i >= 0; i--){
		if(addreq(&addr.digis[i],&((struct ax25_call *) axp->user)->addr)){
		    /* found my call, replace it by port call for input iface */
		    ASSIGN(addr.digis[i],usecall);
		    j = i;	/* for timer values calc */
		    while (i >= 0)
			addr.digis[i--].ssid |= REPEATED;
		}
	    }
	    /* create controlblock, but do not yet open connection */
	    if((axpb = cr_ax25(&addr.dest,&addr.source)) != NULLAX25){
		ASSIGN(axpb->addr,addr);
		/* compute timer values depending on real digipeaters */
		axpb->t1.start = t1init * (addr.ndigis - j);
		axpb->t2.start = t2init * (addr.ndigis - j);
		axpb->window = axp->window = axp->maxframe * axp->paclen;
		axpb->r_upcall = axp->r_upcall = digi_recv;
		axpb->t_upcall = axp->t_upcall = digi_send;
		axpb->s_upcall = digi_b_state;
		if(((struct ax25_call *) axp->user)->interface != NULLIF)
		    axpb->interface = ((struct ax25_call *) axp->user)->interface;
		else
		    axpb->interface = axp->interface;
		lapbstate(axpb,SETUP,LAPBNOMS); /* leave it in SETUP state */

		if(axp->interface == axpb->interface) /* both on same band? */
		    axp->t2.start = axpb->t2.start = 0; /* disable T2 for less collisions */
	    } else {
		axp->state = DISCONNECTED;	/* cannot create 2nd conn */
	    }

	    break;

	case DISCONNECTED:
	    /* Originating user disconnected from NetDigi */
	    pax25(tmp,&((struct ax25_call *) axp->user)->addr);
	    if(axpb != NULLAX25) {
		switch (axpb->state)
		{
		case DISCONNECTED:		/* cannot be */
		case DISCPENDING:		/* avoid double-disconnect */
		    break;

		case SETUP:			/* still attempting */
		    log_ax(axp,"NetDigi %s ab",tmp);
		    disc_ax25(axpb);		/* throw it away */
		    break;

		default:			/* was connected */
		    close_ax25(axpb);		/* graceful close */
		    break;
		}
	    }
	    break;
	}
}

static void
digi_b_state(axp,old,new,msg)
struct ax25_cb *axp;
int old,new,msg;

{
	struct ax25_cb *axpa,*find_ax25();
	char mesg[128],tmp[10],tmp2[10];

	axpa = find_ax25(&axp->addr.source,&axp->addr.dest);
	switch (new)
	{
	case CONNECTED:
	    /* Destination succesfully connected */
	    if(old == SETUP && msg == LAPBCONN && axpa != NULLAX25) {
		pax25(tmp,&((struct ax25_call *) axpa->user)->addr);
		log_ax(axp,"NetDigi %s ok",tmp);  /* log it & fall through */
	    } else
		break;

	case DISCONNECTED:
	    if(axpa != NULLAX25 && msg != LAPBDISC) {
		pax25(tmp,&((struct ax25_call *) axpa->user)->addr);
		pax25(tmp2,&axp->addr.dest);
		sprintf(mesg,"%cNetDigi %s: %s %s\r",
			PID_FIRST|PID_LAST|PID_NO_L3,tmp,ax25mesgs[msg],tmp2);

		send_ax25(axpa,qstring(mesg));
		break;
	    }

	    if(axpa != NULLAX25 && new == DISCONNECTED)
		close_ax25(axpa);

	    break;
	}
}

/* Net Digi receive upcall handler. Transmit data on the other connection,
 * but hold it when that would overflow the transmit queue */
static void
digi_recv(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	register struct mbuf *bp;
	struct mbuf *recv_ax25();
	struct ax25_cb *axp_other,*find_ax25();

	/* find connection to the other station */
	if((axp_other = find_ax25(&axp->addr.source,&axp->addr.dest)) == NULLAX25) {
	    if (axp->state != DISCPENDING)
		disc_ax25(axp);		/* it is gone, so disconnect */
	    return;
	}

	if(axp_other->txq != NULLBUF)	/* transmitter not empty? */
	    return;			/* then hold incoming data */

	if((bp = recv_ax25(axp,cnt)) == NULLBUF)
		return;

	sendp_ax25(axp_other,bp,PID_FIRST|PID_LAST|PID_NO_L3);
}

/* Net Digi transmit upcall handler. Transmit data left over on receive queue */
static void
digi_send(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	register struct mbuf *bp;
	struct mbuf *recv_ax25();
	struct ax25_cb *axp_other,*find_ax25();

	/* find connection to the other station */
	if((axp_other = find_ax25(&axp->addr.source,&axp->addr.dest)) == NULLAX25) {
	    if (axp->state != DISCPENDING)
		disc_ax25(axp);		/* it is gone, so disconnect */
	    return;
	}

	if(axp_other->state == SETUP){	/* still in SETUP state? */
	    sendctl(axp_other,COMMAND,SABM|PF); /* then kick it */
	    return;
	}

	if((bp = recv_ax25(axp_other,cnt)) == NULLBUF)
	    return;

	sendp_ax25(axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);
}


#ifdef MHEARD
/* Start up mheard server */
static
mheard_start(argc,argv)
int argc;
char *argv[];
{
	return ax_sstart(MHEARDPORT,cen(e_ax25,mheard_send,mheard_state);
}

/* Shut down mheard server */
static
mheard_stop()
{
	return ax_sstop(MHEARDPORT);
}

static void
mheard_state(axp,old,new,msg)
register struct ax25_cb *axp;
int old,new,msg;

{
	struct interface *ifp;
	char mesg[80],tmp[10];

	if (old == DISCONNECTED && new == CONNECTED){
	    if(((struct ax25_call *) axp->user)->interface != NULLIF)
		ifp = ((struct ax25_call *) axp->user)->interface;
	    else
		ifp = axp->interface;

	    pax25(tmp,ifp->hwaddr);		/* show it's call */
	    log_ax(axp,"MHEARD %s rq",tmp);
	    sprintf(mesg,"%cMHEARD on %s(%s):\r",
			 PID_FIRST|PID_LAST|PID_NO_L3,tmp,ifp->name);

	    /* must not call send_ax25 because it would send out the
	       m= axpage even before the UA was transmitted */
	    enqueue(&axp->txq,qstring(mesg));
	}
}

/* Mheard transmit upcall handler. Transmit the MHEARD list */
static void
mheard_send(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	struct mbuf *bp = NULLBUF;
	struct interface *ifp;

	if(((struct ax25_call *) axp->user)->interface != NULLIF)
	    ifp = ((struct ax25_call *) axp->user)->interface;
	else
	    ifp = axp->interface;

	showmheard(&bp,ifp);
	append(&bp,qstring("PSE DISC. "));
	sendp_ax25(axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);

	axp->t_upcall = NULLVFP;		/* nothing more to send */
}
#endif


/* bridge server data */
/* each active user on the bridge has an entry in this list */
/* the list head is in the user pointer of the port of this bridge */

struct bridge
{
	struct bridge *next;		/* forward pointer in user list */
	struct bridge *prev;		/* backward pointer in user list */
	struct ax25_cb *axp;		/* connection to this user */
	struct bridge *cur_station;	/* station currently talking to him */
	char name[10];			/* user name in ASCII */
};
#define	 NULLBRIDGE	(struct bridge *)0

struct bridge *bridge_user;		/* used in command exec routines */

/* // commands available from conference bridge */
static int bridge_conn(),bridge_disc(),bridge_interf(),bridge_mheard(),bridge_users();
static struct cmds bridgecmds[] = {
	"connect",	bridge_conn,	0, NULLCHAR,  NULLCHAR,
	"disconnect",	bridge_disc,	0, NULLCHAR,  NULLCHAR,
	"interface",	bridge_interf,	0, NULLCHAR,  NULLCHAR,
#ifdef MHEARD
	"mheard",	bridge_mheard,	0, NULLCHAR,  NULLCHAR,
#endif
	"users",	bridge_users,	0, NULLCHAR,  NULLCHAR,
	NULLCHAR,	NULLFP,		0, NULLCHAR,  NULLCHAR
};

static char bridgehelp[] = "  Commands are:\r\
 //Conn station [int]\r\
 //Disc [station]\r\
 //Int\r\
 //Mheard [int]\r\
 //Users\r";

/* Start up bridge server */
static
bridge_start(argc,argv)
int argc;
char *argv[];
{
	return ax_sstart(BRIDGEPORT,bridge_recv,NULLVFP,bridge_state);
}

/* Shut down bridge server */
static
bridge_stop()
{
	return ax_sstop(BRIDGEPORT);
}

/* find bridge struct for specified axp */

static struct bridge *
find_bridge(axp)
register struct ax25_cb *axp;
{
	register struct bridge *brp;

	for (brp = (struct bridge *) ((struct ax25_call *) axp->user)->user;
	     brp != NULLBRIDGE; brp = brp->next) {
		if(brp->axp == axp)
		    break;
	}

	return (brp);
}

/* send data to all users on bridge, EXCEPT specified user */
/* prefix data with user name, and ": ", when station changes */

static void
send_bridge(org,bp)
struct bridge *org;			/* originating connection */
struct mbuf *bp;			/* data to send */
{
	register struct bridge *brp;
	struct mbuf *tbp;
	int n,llen = -1;
	register char *p;
	char tmp[20];

	if (bp == NULLBUF)		/* nothing to send? */
		return;

	for (brp = (struct bridge *) ((struct ax25_call *) org->axp->user)->user;
	     brp != NULLBRIDGE; brp = brp->next) {
	    if(brp != org) {
		if(org != brp->cur_station) {	/* another station takes the mike */
		    brp->cur_station = org;

		    if (llen < 0){		/* compute 1st line length? */
			llen = 0;
			for(tbp = bp;tbp != NULLBUF; tbp = tbp->next){
			    for(n = tbp->cnt,p = tbp->data; n > 0; n--){
				if (*p++ == '\r')
				    goto found;
				llen++;
			    }
			}
found:			;
		    }

		    sprintf(tmp,"%s:%c",org->name,
			    (llen + strlen(org->name) > 77? '\r':' '));
		    tbp = qstring(tmp);
		    append(&tbp,copy_p(bp,len_mbuf(bp)));
		} else {
		    tbp = copy_p(bp,len_mbuf(bp));
		}

		sendp_ax25(brp->axp,tbp,PID_FIRST|PID_LAST|PID_NO_L3);
	    }
	}
	free_p(bp);
}


static void
bridge_state(axp,old,new,msg)
register struct ax25_cb *axp;
int old,new,msg;

{
	register struct bridge *brp;
	struct bridge *brp2;
	char mesg[160],tmp[10];
	void disc_ax25();

	switch (new)
	{
	case CONNECTED:
	    if (old != DISCONNECTED && old != SETUP) /* not a new user? */
		return;				/* ignore it */

	    pax25(tmp,&((struct ax25_call *) axp->user)->addr);

	    /* first check for reasonable digi path and send message if
	     * too many digis.	station will be disconnected when this
	     * m= axpage is acknowledged.
	     */
	    if(axp->addr.ndigis > 1) {
		sprintf(mesg,"%c%s conference bridge: too many digipeaters\r",
			     PID_FIRST|PID_LAST|PID_NO_L3,tmp);

		/* must not call send_ax25 because it would send out the
		   m= axpage even before the UA was transmitted */
		enqueue(&axp->txq,qstring(mesg));
		axp->t_upcall = disc_ax25;
		return;
	    }

	    /* check if station already known, and register if not */
	    if((brp = find_bridge(axp)) == NULLBRIDGE) {
		if((brp = (struct bridge *) calloc(1,sizeof(struct bridge)))
			 == NULLBRIDGE) {
		    axp->state = DISCONNECTED;
		    return;
		}

		/* connect the user to this bridge's user list */
		if((brp->next = (struct bridge *) ((struct ax25_call *) axp->user)->user)
			 != NULLBRIDGE)
		    brp->next->prev = brp;

		((struct ax25_call *) axp->user)->user = (char *) brp;
		brp->axp = axp;
		pax25(brp->name,&axp->addr.dest);
		send_bridge(brp,qstring("*** CONNECTED\r"));
		log_ax(axp,"Conn to bridge");

		sprintf(mesg,"%cWelcome to the %s conference bridge.%s",
			     PID_FIRST|PID_LAST|PID_NO_L3,tmp,bridgehelp);

		enqueue(&axp->txq,qstring(mesg));
	    }

	    axp->t2.start = 0;		/* disable T2 for less collisions */
	    break;

	case DISCONNECTED:
	    /* Bridge user got disconnected, or cannot be connected */
	    if((brp = find_bridge(axp)) != NULLBRIDGE) {
		if(brp->prev != NULLBRIDGE)
		    brp->prev->next = brp->next;
		else
		    ((struct ax25_call *) axp->user)->user = (char *) brp->next;

		if(brp->next != NULLBRIDGE)
		    brp->next->prev = brp->prev;

		/* Reset everybody's current station so that the status */
		/* change will always be reported as CALL: *** .... */
		for(brp2 = (struct bridge *) ((struct ax25_call *) axp->user)->user;
		    brp2 != NULLBRIDGE; brp2 = brp2->next)
		    brp2->cur_station = NULLBRIDGE;

		if(msg == LAPBFAIL)
		    send_bridge(brp,qstring("*** LINK FAILURE\r"));
		else
		    send_bridge(brp,qstring("*** DISCONNECTED\r"));
		free(brp);
		log_ax(axp,"Disc from bridge");
	    } else {
		/* was not in the list, probably cannot be connected */
		switch (msg)
		{
		case LAPBBUSY:
		case LAPBFAIL:
		    /* have to send some message to the requestor */
		    break;
		}
	    }
	    break;
	}
}

/* bridge receive upcall handler. Transmit data to the other stations */
static void
bridge_recv(axp,cnt)
struct ax25_cb *axp;
int16 cnt;
{
	struct bridge *brp;
	struct mbuf *bp,*tbp;
	char cmdline[80];
	register char *p;
	struct mbuf *recvl_ax25();

	if((brp = find_bridge(axp)) == NULLBRIDGE) {
	    disc_ax25(axp);			/* who are you?? */
	    return;
	}

	tbp = NULLBUF;
	while((bp = recvl_ax25(axp,0)) != NULLBUF){ /* get a line of text */
	    if(bp->cnt > 3 &&			    /* at least 4 chars */
	       bp->data[0] == '/' && bp->data[1] == '/'){ /* //.... */
		if (bp->data[2] == '/'){	/* ///command */
		    (void) pullchar(&bp);	/* discard 1st slash */
		    if (tbp == NULLBUF)		/* first line in packet? */
			tbp = qstring("\r");	/* make sure a CR is before // */
		    append(&tbp,bp);		/* and pass on to DIGICOM... */
		    continue;
		}
		cmdline[pullup(&bp,cmdline,sizeof(cmdline) - 1)] = '\0';
		free_p(bp);
		for(p = cmdline; *p != '\0'; p++)/* convert to lower case */
		    *p = tolower(*p);
		bridge_user = brp;		/* store for execution routine */
		if (index(cmdline,'$') != NULLCHAR || /* a $ is not allowed */
		    cmdparse(bridgecmds,cmdline + 2) < 0) { /* skip // in parsing */
		    bp = qstring("Error in //command.");
		    append(&bp,qstring(bridgehelp));
		    sendp_ax25(axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);
		}

		brp->cur_station = NULLBRIDGE;	/* force station name print */
	    } else {
		append(&tbp,bp);		/* if info, keep it */
	    }
	}

	send_bridge(brp,tbp);			/* pass info to other users */
}

/* Process // commands from the conference bridge */

static int
bridge_conn(argc,argv)
int argc;
char *argv[];
{
	struct ax25 addr;
	struct ax25_cb *axp = bridge_user->axp;
	struct ax25_cb *axp2,*find_ax25(),*open_ax25();
	struct interface *ifp;

	if (argc > 1){
	    if(argc > 2){
		if((ifp = if_lookup(argv[2])) == NULLIF ||
		   !(ifp->flags & IF_AX25)){
		    sendp_ax25(bridge_user->axp,qstring("Interface unknown\r"),
			       PID_FIRST|PID_LAST|PID_NO_L3);
		    return 0;
	    }
	    } else {
		ifp = axp->interface;
	    }

	    if (setcall(&addr.dest,argv[1]) != 0){
		sendp_ax25(axp,qstring("Connect: Bad CALL\r"),
			   PID_FIRST|PID_LAST|PID_NO_L3);
		return 0;
	    }

	    ASSIGN(addr.source,((struct ax25_call *) axp->user)->addr);
	    addr.ndigis = 0;
	    if (find_ax25(&addr.dest,&addr.source) != NULLAX25){
		sendp_ax25(axp,qstring("Already connected to that station\r"),
			   PID_FIRST|PID_LAST|PID_NO_L3);
		return 0;
	    }

	    if ((axp2 = open_ax25(&addr,axp->window,
				  axp->r_upcall,axp->t_upcall,axp->s_upcall,
				  ifp,axp->user)) != NULLAX25){
		axp2->t2.start = 0;	/* T2 disabled */
		log_ax(axp,"Bridge conrq %s (%s)",argv[1],ifp->name);
	    } else {
		sendp_ax25(axp,qstring("Connect fails\r"),
			   PID_FIRST|PID_LAST|PID_NO_L3);
		return 0;
	    }
	    return 0;
	}
	return -1;
}

static int
bridge_disc(argc,argv)
int argc;
char *argv[];
{
	struct ax25 addr;
	struct bridge *brp;
	struct ax25_cb *axp = bridge_user->axp;

	if (argc > 1){
	    if (setcall(&addr.dest,argv[1]) == 0){
		for (brp = (struct bridge *) ((struct ax25_call *) axp->user)->user;
		     brp != NULLBRIDGE; brp = brp->next)
		    if(!strcmp(brp->name,argv[1])){
			if(brp->axp->state != DISCPENDING)
			    disc_ax25(brp->axp);
			log_ax(axp,"Bridge discrq %s",argv[1]);
			return 0;
		    }
	    }
	    sendp_ax25(axp,qstring("Disconnect: Bad CALL\r"),
		       PID_FIRST|PID_LAST|PID_NO_L3);
	    return 0;
	}
	close_ax25(axp);			/* no args, disc myself */
	return 0;
}

static int
bridge_interf(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	register struct interface *ifp;

	bp = qstring("Interfaces:");

	for (ifp = ifaces; ifp != NULLIF; ifp = ifp->next){
	    if(ifp->flags & IF_AX25){
		append(&bp,qstring(" "));
		append(&bp,qstring(ifp->name));
	    }
	}

	append(&bp,qstring("\r"));
	sendp_ax25(bridge_user->axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);
	return 0;
}

#ifdef MHEARD
static int
bridge_mheard(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp = NULLBUF;	/* clear startoff */
	struct interface *ifp;

	if(argc > 1){
	    if((ifp = if_lookup(argv[1])) == NULLIF ||
		!(ifp->flags & IF_AX25)){
		sendp_ax25(bridge_user->axp,qstring("Interface unknown\r"),
			   PID_FIRST|PID_LAST|PID_NO_L3);
		return 0;
	    }
	} else {
	    ifp = bridge_user->axp->interface;
	}

	showmheard(&bp,ifp);		/* collect mheard */
	append(&bp,qstring("Mheard ends\r"));
	sendp_ax25(bridge_user->axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);
	return 0;
}
#endif

static int
bridge_users(argc,argv)
int argc;
char *argv[];
{
	struct mbuf *bp;
	register struct bridge *brp;
	char buf[30];

	bp = qstring("Users:");

	for (brp = (struct bridge *) ((struct ax25_call *) bridge_user->axp->user)->user;
	     brp != NULLBRIDGE; brp = brp->next) {
	    sprintf(buf," %s(%s)",brp->name,brp->axp->interface->name);
	    append(&bp,qstring(buf));
	}

	append(&bp,qstring("\r"));
	sendp_ax25(bridge_user->axp,bp,PID_FIRST|PID_LAST|PID_NO_L3);
	return 0;
}

#ifdef TNC2
/* Start up tnc2 emulator server */
static
tnc2_start(argc,argv)
int argc;
char *argv[];
{
	register int i;
	int rv,dev = 0;
	struct interface *ifp;
	register struct tnc *tnc2;
	struct ax25_call *axc;
	char *ifname;
	void tnc2_state(),tnc2_tick();
	extern char tnc2_prompt[];
	extern int tnc2_init();

	if ((rv = ax_sstart(TNC2PORT,NULLVFP,NULLVFP,tnc2_state)) != 0)
	    return rv;

	for (i = 1; i < argc; i++) {
	    if ((ifname = index(argv[i],'=')) != NULLCHAR) {
		dev = atoi(argv[i]);		/* dev=interface */
		ifname++;
	    } else {
		dev++;				/* interface (next device) */
		ifname = argv[i];
	    }
	    for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next)
		if (tnc2->dev == dev)
		   break;
	    if (tnc2 != NULLTNC) {
		printf("tnc2: device %d duplicate\n",dev);
		continue;
	    }
	    if ((ifp = ifunit(ifname)) != NULLIF) {
		if ((tnc2 = (struct tnc *) calloc(1,sizeof(struct tnc))) == NULLTNC){
		    printf(nospace);
		    return -1;
		}
		tnc2->dev = dev;
		if (tnc2_init(tnc2)) {		/* init comm port */
		    free(tnc2);			/* when it fails, skip rest */
		    rv |= 1;
		    continue;
		}
		tnc2->interface = ifp;
		for(axc = ax25_port; axc != NULLAXCALL; axc = axc->p_next){
		    if(axc->port == TNC2PORT &&
		       (axc->interface == ifp || (axc->flags & MULTI_IF))){
			tnc2->axcall = axc;
			break;
		    }
		}
		tnc2->next = tnc2s;		/* link it in the list */
		tnc2s = tnc2;
		dotnreset(tnc2);		/* set default values */
		if (tnc2->mode == TM_CMD)
		    tnc2_mesg(tnc2,tnc2_prompt); /* initial prompt */
	    } else {
		rv |= 1;
	    }
	}

	return rv;
}

/* Shut down tnc2 emulator server */
static
tnc2_stop()
{
	register struct tnc *tnc2;
	int i;

	for (tnc2 = tnc2s; tnc2 != NULLTNC; tnc2 = tnc2->next)
	    if (tnc2->axp != NULLAX25){
		printf("AX.25 connection(s) to TNC2 present\n");
		return -1;
	    }

	while ((tnc2 = tnc2s) != NULLTNC) {
	    tnc2s = tnc2->next;
	    tnc2_term(tnc2);
	    for (i = 0; i < TNCTEXTS; i++)
		if (tnc2->text[i] != NULLCHAR)
		    free(tnc2->text[i]);
	    for (i = 0; i < TNCTIMERS; i++)
		stop_timer(&tnc2->timer[i]);
	    free(tnc2);
	}

	return ax_sstop(TNC2PORT);
}
#endif

# ifdef SMTPC /* mailbox needs SMTP client! */
/* Start up mailbox server */
static
mbox_start(argc,argv)
int argc;
char *argv[];
{
	int rv;
	extern char mbxinfofile[];
	void mbx_state();

	if (argc > 1)
	    strcpy(mbxinfofile,argv[1]);
	else
	    mbxinfofile[0] = '\0';

	if ((rv = ax_sstart(MBOXPORT,NULLVFP,NULLVFP,mbx_state)) != 0)
	    return rv;

	return 0;
}

/* Shut down mailbox server */
static
mbox_stop()
{
	return ax_sstop(MBOXPORT);
}
# endif
#e