/***************************************************************************** * * Purpose ...............: Fidonet mailer * ***************************************************************************** * Copyright (C) 1997-2011 * * Michiel Broek FIDO: 2:280/2802 * Beekmansbos 10 * 1971 BV IJmuiden * the Netherlands * * This file is part of MBSE BBS. * * This BBS is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2, or (at your option) any * later version. * * MBSE BBS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MBSE BBS; see the file COPYING. If not, write to the Free * Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *****************************************************************************/ #include "../config.h" #include "../lib/mbselib.h" #include "../lib/nodelist.h" #include "ttyio.h" #include "statetbl.h" #include "emsi.h" #include "ftsc.h" #include "session.h" #include "yoohoo.h" #include "mbcico.h" #include "binkp.h" #include "callstat.h" #include "inbound.h" #include "opentcp.h" #include "telnet.h" extern int tcp_mode; extern pid_t mypid; node *nlent = NULL; fa_list *remote = NULL; int session_flags; int remote_flags; int laststat = 0; /* Last session status with remote */ int tx_define_type(void); int rx_define_type(void); int session_type = SESSION_UNKNOWN; int session_state = STATE_BAD; static char *data=NULL; struct sockaddr_in peeraddr4; struct sockaddr_in6 peeraddr6; char *typestr(int); char *typestr(int tp) { switch (tp) { case SESSION_FTSC: return (char *)"FTS-0001"; case SESSION_YOOHOO: return (char *)"YooHoo/2U2"; case SESSION_EMSI: return (char *)"EMSI"; case SESSION_BINKP: return (char *)"Binkp"; default: return (char *)"Unknown"; } } #ifdef HAVE_GEOIP_H extern void _GeoIP_setup_dbfilename(void); void geoiplookup(GeoIP* gi, char *hostname, int i) { const char * country_code; const char * country_name; const char * country_continent; int country_id; if (GEOIP_COUNTRY_EDITION == i) { country_id = GeoIP_id_by_name(gi, hostname); country_code = GeoIP_country_code[country_id]; country_name = GeoIP_country_name[country_id]; country_continent = GeoIP_country_continent[country_id]; if (country_code == NULL) { Syslog('+', "%s: IP Address not found\n", GeoIPDBDescription[i]); } else { Syslog('+', "GeoIP location: %s, %s %s\n", country_name, country_code, country_continent); } } if (GEOIP_COUNTRY_EDITION_V6 == i) { country_id = GeoIP_id_by_name_v6(gi, hostname); country_code = GeoIP_country_code[country_id]; country_name = GeoIP_country_name[country_id]; country_continent = GeoIP_country_continent[country_id]; if (country_code == NULL) { Syslog('+', "%s: IP Address not found\n", GeoIPDBDescription[i]); } else { Syslog('+', "GeoIP location: %s, %s %s\n", country_name, country_code, country_continent); } } } #endif int session(faddr *a, node *nl, int role, int tp, char *dt) { int rc = MBERR_OK; socklen_t addrlen = sizeof(struct sockaddr_in6); fa_list *tmpl; int Fdo = -1, input_pipe[2], output_pipe[2]; pid_t ipid, opid; char str[INET6_ADDRSTRLEN]; #ifdef HAVE_GEOIP_H GeoIP *gi; #endif session_flags = 0; session_type = tp; nlent = nl; if (getpeername(0,(struct sockaddr *)&peeraddr6, &addrlen) == 0) { /* * Copy IPv4 part into the IPv6 structure. There has to be a better way * to deal with mixed incoming sockets ??? */ memcpy(&peeraddr4, &peeraddr6, sizeof(struct sockaddr_in)); if ((peeraddr6.sin6_family == AF_INET6) && (inet_ntop(AF_INET6, &peeraddr6.sin6_addr, str, sizeof(str)))) { Syslog('s', "IPv6 TCP connection: len=%d, port=%hu, addr=%s", addrlen, ntohs(peeraddr6.sin6_port), str); } else if ((peeraddr4.sin_family == AF_INET) && (inet_ntop(AF_INET, &peeraddr4.sin_addr, str, sizeof(str)))) { Syslog('s', "IPv4 TCP connection: len=%d, port=%hu, addr=%s", addrlen, ntohs(peeraddr4.sin_port), str); } if (role == 0) { if (tcp_mode == TCPMODE_IBN) { Syslog('+', "Incoming IBN/TCP connection from %s", str); IsDoing("Incoming IBN/TCP"); } else if (tcp_mode == TCPMODE_IFC) { Syslog('+', "Incoming IFC/TCP connection from %s", str); IsDoing("Incoming IFC/TCP"); } else if (tcp_mode == TCPMODE_ITN) { Syslog('+', "Incoming ITN/TCP connection from %s", str); IsDoing("Incoming ITN/TCP"); } else if (tcp_mode == TCPMODE_NONE) { WriteError("Unknown TCP connection, parameter missing"); die(MBERR_COMMANDLINE); } } session_flags |= SESSION_TCP; #ifdef HAVE_GEOIP_H _GeoIP_setup_dbfilename(); if (peeraddr6.sin6_family == AF_INET6) { if (GeoIP_db_avail(GEOIP_COUNTRY_EDITION_V6)) { if ((gi = GeoIP_open_type(GEOIP_COUNTRY_EDITION_V6, GEOIP_STANDARD)) != NULL) { geoiplookup(gi, str, GEOIP_COUNTRY_EDITION_V6); } GeoIP_delete(gi); } } else if (peeraddr6.sin6_family == AF_INET) { if (GeoIP_db_avail(GEOIP_COUNTRY_EDITION)) { if ((gi = GeoIP_open_type(GEOIP_COUNTRY_EDITION, GEOIP_STANDARD)) != NULL) { geoiplookup(gi, str, GEOIP_COUNTRY_EDITION); } GeoIP_delete(gi); } } #endif if (tcp_mode == TCPMODE_ITN) { Syslog('s', "Installing telnet filter..."); /* * First make sure the current input socket gets a new file descriptor * since it's now on stadin and stdout. */ Fdo = dup(0); /* * Close stdin and stdout so that when we create the pipes to * the telnet filter they get stdin and stdout as file descriptors. */ fflush(stdin); fflush(stdout); setbuf(stdin,NULL); setbuf(stdout, NULL); close(0); close(1); /* * Create output pipe and start output filter. */ if (pipe(output_pipe) == -1) { WriteError("$could not create output_pipe"); die(MBERR_TTYIO_ERROR); } opid = fork(); switch (opid) { case -1: WriteError("fork for telout_filter failed"); die(MBERR_TTYIO_ERROR); case 0: if (close(output_pipe[1]) == -1) { WriteError("$error close output_pipe[1]"); die(MBERR_TTYIO_ERROR); } telout_filter(output_pipe[0], Fdo); /* NOT REACHED */ } if (close(output_pipe[0] == -1)) { WriteError("$error close output_pipe[0]"); die(MBERR_TTYIO_ERROR); } Syslog('s', "telout_filter forked with pid %d", opid); /* * Create input pipe and start input filter */ if (pipe(input_pipe) == -1) { WriteError("$could not create input_pipe"); die(MBERR_TTYIO_ERROR); } ipid = fork(); switch (ipid) { case -1: WriteError("fork for telin_filter failed"); die(MBERR_TTYIO_ERROR); case 0: if (close(input_pipe[0]) == -1) { WriteError("$error close input_pipe[0]"); die(MBERR_TTYIO_ERROR); } telin_filter(input_pipe[1], Fdo); /* NOT REACHED */ } if (close(input_pipe[1]) == -1) { WriteError("$error close input_pipe[1]"); die(MBERR_TTYIO_ERROR); } Syslog('s', "telin_filter forked with pid %d", ipid); Syslog('s', "stdout = %d", output_pipe[1]); Syslog('s', "stdin = %d", input_pipe[0]); if ((input_pipe[0] != 0) || (output_pipe[1] != 1)) { WriteError("Failed to create pipes on stdin and stdout"); die(MBERR_TTYIO_ERROR); } Syslog('+', "Telnet I/O filters installed"); telnet_init(Fdo); } } if (data) free(data); data=NULL; if (dt) data=xstrcpy(dt); emsi_local_protos=0; emsi_local_opts=0; emsi_local_lcodes=0; tidy_falist(&remote); remote=NULL; if (a) { remote=(fa_list*)malloc(sizeof(fa_list)); remote->next=NULL; remote->addr=(faddr*)malloc(sizeof(faddr)); remote->addr->zone=a->zone; remote->addr->net=a->net; remote->addr->node=a->node; remote->addr->point=a->point; remote->addr->domain=xstrcpy(a->domain); remote->addr->name=NULL; } else { remote=NULL; } remote_flags=SESSION_FNC; if (role) { if (session_type == SESSION_UNKNOWN) (void)tx_define_type(); Syslog('+', "Start outbound %s session with %s", typestr(session_type), ascfnode(a,0x1f)); switch(session_type) { case SESSION_UNKNOWN: rc = MBERR_UNKNOWN_SESSION; break; case SESSION_FTSC: rc = tx_ftsc(); break; case SESSION_YOOHOO: rc = tx_yoohoo(); break; case SESSION_EMSI: rc = tx_emsi(data); break; case SESSION_BINKP: rc = binkp(role); break; } } else { if (session_type == SESSION_FTSC) session_flags |= FTSC_XMODEM_CRC; if (session_type == SESSION_UNKNOWN) (void)rx_define_type(); Syslog('+', "Start inbound %s session", typestr(session_type)); switch(session_type) { case SESSION_UNKNOWN: rc = MBERR_UNKNOWN_SESSION; break; case SESSION_FTSC: rc = rx_ftsc(); break; case SESSION_YOOHOO: rc = rx_yoohoo(); break; case SESSION_EMSI: rc = rx_emsi(data); break; case SESSION_BINKP: rc = binkp(role); break; } } sleep(2); for (tmpl = remote; tmpl; tmpl = tmpl->next) { /* * Unlock all nodes, locks not owned by us are untouched. */ (void)nodeulock(tmpl->addr, mypid); /* * If successfull session, reset all status records. */ if (rc == 0) putstatus(tmpl->addr, 0, 0); } if (rc) session_state = STATE_BAD; /* * If the socket for the telnet filter is open, close it so that the telnet filters exit. * After that wait a little while to let the filter childs die before the main program * does, else we get zombies. */ if (Fdo != -1) { Syslog('s', "shutdown filter sockets and stdio"); shutdown(Fdo, 2); close(0); close(1); close(output_pipe[1]); close(input_pipe[0]); msleep(100); } tidy_falist(&remote); if (data) free(data); data = NULL; if (emsi_local_password) free(emsi_local_password); if (emsi_remote_password) free(emsi_remote_password); inbound_close(rc == 0); return rc; } SM_DECL(tx_define_type,(char *)"tx_define_type") SM_STATES skipjunk, wake, waitchar, nextchar, checkintro, sendinq SM_NAMES (char *)"skipjunk", (char *)"wake", (char *)"waitchar", (char *)"nextchar", (char *)"checkintro", (char *)"sendinq" SM_EDECL int c = 0; char buf[256], *p; char ebuf[13], *ep; int standby = 0; int maybeftsc=0; int maybeyoohoo=0; session_type = SESSION_UNKNOWN; ebuf[0] = '\0'; ep = ebuf; buf[0] = '\0'; p = buf; SM_START(skipjunk) SM_STATE(skipjunk) while ((c = GETCHAR(1)) >= 0) /*nothing*/ ; if (c == TIMEOUT) { gpt_resettimers(); gpt_settimer(0, 60); /* 60 second master timer */ SM_PROCEED(wake); } else { SM_ERROR; } SM_STATE(wake) if (gpt_expired(0)) { Syslog('+', "Remote did not respond"); SM_ERROR; } p = buf; PUTCHAR('\r'); if ((c = GETCHAR(2)) == TIMEOUT) { SM_PROCEED(wake); } else if (c < 0) { WriteError("Error while waking remote"); SM_ERROR; } else { gpt_settimer(0, 60); SM_PROCEED(nextchar); } SM_STATE(waitchar) if ((c = GETCHAR(2)) == TIMEOUT) { standby = 0; ep = ebuf; ebuf[0] = '\0'; if (gpt_expired(0)) { Syslog('+', "Too many tries waking remote"); SM_ERROR; } SM_PROCEED(sendinq); } else if (c < 0) { Syslog('+', "Error while getting intro from remote"); SM_ERROR; } else { SM_PROCEED(nextchar); } SM_STATE(nextchar) if (c == 'C') { session_flags |= FTSC_XMODEM_CRC; maybeftsc++; } if (c == NAK) { session_flags &= ~FTSC_XMODEM_CRC; maybeftsc++; } if (c == ENQ) maybeyoohoo++; if (((localoptions & NOWAZOO) == 0) && (maybeyoohoo > 1)) { session_type = SESSION_YOOHOO; SM_SUCCESS; } if (maybeftsc > 1) { session_type = SESSION_FTSC; SM_SUCCESS; } if ((c >= ' ') && (c <= '~')) { if (c != 'C') maybeftsc = 0; maybeyoohoo = 0; if ((p-buf) < (sizeof(buf)-1)) { *p++ = c; *p = '\0'; } if (c == '*') { standby = 1; ep = ebuf; buf[0] = '\0'; } else if (standby) { if ((ep - ebuf) < (sizeof(ebuf) - 1)) { *ep++ = c; *ep = '\0'; } if ((ep - ebuf) >= (sizeof(ebuf) - 1)) { standby = 0; SM_PROCEED(checkintro); } } } else { switch (c) { case DC1: break; case '\r': case '\n': standby = 0; ep = ebuf; ebuf[0] = '\0'; if (buf[0]) Syslog('+', "Intro: \"%s\"", printable(buf, 0)); p = buf; buf[0] = '\0'; break; default: standby = 0; ep = ebuf; ebuf[0] = '\0'; Syslog('i', "Got '%s' reading intro", printablec(c)); break; } } SM_PROCEED(waitchar); SM_STATE(checkintro) Syslog('i', "Check \"%s\" for being EMSI request",ebuf); if (((localoptions & NOEMSI) == 0) && (strncasecmp(ebuf,"EMSI_REQA77E",12) == 0)) { session_type = SESSION_EMSI; data = xstrcpy((char *)"**EMSI_REQA77E"); Syslog('i', "Sending **EMSI_INQC816 (2 times)"); PUTSTR((char *)"\r**EMSI_INQC816\r**EMSI_INQC816\r\021"); SM_SUCCESS; } else { p = buf; buf[0] = '\0'; SM_PROCEED(waitchar); } SM_STATE(sendinq) PUTCHAR(DC1); if ((localoptions & NOEMSI) == 0) { PUTSTR((char *)"\r**EMSI_INQC816\r**EMSI_INQC816"); } if ((localoptions & NOWAZOO) == 0) { PUTCHAR(YOOHOO); } PUTCHAR(TSYNC); if ((localoptions & NOEMSI) == 0) PUTSTR((char *)"\r\021"); SM_PROCEED(waitchar); SM_END SM_RETURN SM_DECL(rx_define_type,(char *)"rx_define_type") SM_STATES sendintro, settimer, waitchar, nextchar, checkemsi, getdat SM_NAMES (char *)"sendintro", (char *)"settimer", (char *)"waitchar", (char *)"nextchar", (char *)"checkemsi", (char *)"getdat" SM_EDECL int count=0; int c=0; int maybeftsc=0,maybeyoohoo=0; char ebuf[13],*ep; char *p; int standby=0; int datasize; session_type=SESSION_UNKNOWN; session_flags|=FTSC_XMODEM_CRC; ebuf[0]='\0'; ep=ebuf; gpt_resettimers(); gpt_settimer(0, 60); gpt_settimer(1, 20); SM_START(sendintro) SM_STATE(sendintro) if (count++ > 6) { Syslog('+', "Too many tries to get anything from the caller"); SM_ERROR; } Syslog('s', "rxdefine_type SENDINTRO count=%d", count); standby = 0; ep = ebuf; ebuf[0] = '\0'; if ((localoptions & NOEMSI) == 0) { PUTSTR((char *)"**EMSI_REQA77E\r\021"); } PUTCHAR('\r'); if (STATUS) { SM_ERROR; } else { SM_PROCEED(settimer); } SM_STATE(settimer) Syslog('s', "Set 20 secs timer"); gpt_settimer(1, 20); SM_PROCEED(waitchar); SM_STATE(waitchar) if (gpt_expired(0)) { Syslog('+', "Session setup timeout"); SM_ERROR; } if (gpt_expired(1)) { Syslog('s', "20 sec timer timeout"); SM_PROCEED(sendintro); } if ((c = GETCHAR(1)) == TIMEOUT) { SM_PROCEED(waitchar); } else if (c < 0) { Syslog('+', "Session setup error"); SM_ERROR; } else { SM_PROCEED(nextchar); } SM_STATE(nextchar) if ((c >= ' ') && (c <= 'z')) { if (c == '*') { standby = 1; ep = ebuf; ebuf[0] = '\0'; } else if (standby) { if ((ep - ebuf) < (sizeof(ebuf) - 1)) { *ep++ = c; *ep = '\0'; } if ((ep - ebuf) >= (sizeof(ebuf) - 1)) { standby = 0; SM_PROCEED(checkemsi); } } SM_PROCEED(waitchar); } else { switch (c) { case DC1: SM_PROCEED(waitchar); break; case TSYNC: standby = 0; ep = ebuf; ebuf[0] = '\0'; if (++maybeftsc > 1) { session_type = SESSION_FTSC; SM_SUCCESS; } else { SM_PROCEED(waitchar); } break; case YOOHOO:standby = 0; ep = ebuf; ebuf[0] = '\0'; if (++maybeyoohoo > 1) { session_type = SESSION_YOOHOO; SM_SUCCESS; } else { SM_PROCEED(waitchar); } break; case '\r': case '\n': standby = 0; ep = ebuf; ebuf[0] = '\0'; if (ebuf[0]) { SM_PROCEED(checkemsi); } else { /* * If the 20 second timer is expired or after the * first sendintro, send the intro again. After * that take it easy. */ if (gpt_expired(1) || (count == 1)) { Syslog('s', "sendintro after eol char"); SM_PROCEED(sendintro); } else { Syslog('s', "waitchar after eol char"); SM_PROCEED(waitchar); } } break; default: standby = 0; ep = ebuf; ebuf[0] = '\0'; Syslog('i', "Got '%s' from remote", printablec(c)); SM_PROCEED(waitchar); break; } } SM_STATE(checkemsi) Syslog('i', "check \"%s\" for being EMSI inquery or data",ebuf); if (localoptions & NOEMSI) { Syslog('s', "Force sendintro"); SM_PROCEED(sendintro); } if (strncasecmp(ebuf, "EMSI_INQC816", 12) == 0) { session_type = SESSION_EMSI; data = xstrcpy((char *)"**EMSI_INQC816"); SM_SUCCESS; } else if (strncasecmp(ebuf, "EMSI_HBT", 8) == 0) { standby = 0; ep = ebuf; ebuf[0] = '\0'; SM_PROCEED(settimer); } else if (strncasecmp(ebuf, "EMSI_DAT", 8) == 0) { SM_PROCEED(getdat); } else { SM_PROCEED(settimer); } SM_STATE(getdat) Syslog('i', "Try get emsi_dat packet starting with \"%s\"",ebuf); if (sscanf(ebuf+8, "%04x", &datasize) != 1) { SM_PROCEED(sendintro); } datasize += 18; /* strlen("**EMSI_DATxxxxyyyy"), include CRC */ data=malloc(datasize+1); strcpy(data,"**"); strcat(data, ebuf); p = data + strlen(data); while (((p - data) < datasize) && ((c = GETCHAR(8)) >= 0)) { *p++ = c; *p= '\0'; } if (c == TIMEOUT) { Syslog('s', "c = TIMEOUT -> sendintro"); SM_PROCEED(sendintro); } else if (c < 0) { Syslog('+', "Error while reading EMSI_DAT from the caller"); SM_ERROR; } session_type = SESSION_EMSI; SM_SUCCESS; SM_END SM_RETURN