/* * $Id: ntripserver.c 9812 2022-08-23 14:30:54Z stoecker $ * * Copyright (c) 2003...2019 * German Federal Agency for Cartography and Geodesy (BKG) * Dirk Stöcker (Alberding GmbH) * * Developed for Networked Transport of RTCM via Internet Protocol (NTRIP) * for streaming GNSS data over the Internet. * * Designed by Informatik Centrum Dortmund http://www.icd.de * * The BKG disclaims any liability nor responsibility to any person or * entity with respect to any loss or damage caused, or alleged to be * caused, directly or indirectly by the use and application of the NTRIP * technology. * * For latest information and updates, access: * https://igs.bkg.bund.de/ntrip/index * * BKG, Frankfurt, Germany, August 2019 * E-mail: euref-ip@bkg.bund.de * * This program 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 * of the License, or (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* SVN revision and version */ static char revisionstr[] = "$Revision: 9812 $"; static char datestr[] = "$Date: 2022-08-23 14:30:54 +0000 (Tue, 23 Aug 2022) $"; #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WINDOWSVERSION #include #include #include #include typedef SOCKET sockettype; typedef u_long in_addr_t; typedef size_t socklen_t; typedef u_short uint16_t; #else typedef int sockettype; #include #include #include #include #include #define closesocket(sock) close(sock) #define INVALID_HANDLE_VALUE -1 #define INVALID_SOCKET -1 #endif #ifndef COMPILEDATE #define COMPILEDATE " built " __DATE__ #endif #define ALARMTIME (2*60) #ifndef MSG_DONTWAIT #define MSG_DONTWAIT 0 /* prevent compiler errors */ #endif #ifndef O_EXLOCK #define O_EXLOCK 0 /* prevent compiler errors */ #endif enum MODE { SERIAL = 1, TCPSOCKET = 2, INFILE = 3, SISNET = 4, UDPSOCKET = 5, NTRIP1_IN = 6, NTRIP2_HTTP_IN = 7, // HTTP only LAST }; enum OUTMODE { HTTP = 1, RTSP = 2, NTRIP1 = 3, UDP = 4, TCPIP = 5, END }; #define AGENTSTRING "NTRIP NtripServerPOSIX" #define BUFSZ 10240 #define SZ 64 /* default socket source */ #define SERV_HOST_ADDR "localhost" #define SERV_TCP_PORT 2101 /* default destination */ #define NTRIP_CASTER "euref-ip.net" #define NTRIP_PORT 2101 #define SISNET_SERVER "131.176.49.142" #define SISNET_PORT 7777 #define RTP_VERSION 2 #define TIME_RESOLUTION 125 static int ttybaud = 19200; #ifndef WINDOWSVERSION static const char *ttyport = "/dev/gps"; #else static const char *ttyport = "COM1"; #endif static const char *filepath = "/dev/stdin"; static enum MODE inputmode = INFILE; static int sisnet = 31; static int gps_file = -1; static sockettype gps_socket = INVALID_SOCKET; static sockettype socket_tcp = INVALID_SOCKET; static sockettype local_socket_tcp = INVALID_SOCKET; static sockettype socket_udp = INVALID_SOCKET; #ifndef WINDOWSVERSION static int gps_serial = INVALID_HANDLE_VALUE; static int sigpipe_received = 0; #else HANDLE gps_serial = INVALID_HANDLE_VALUE; #endif static int sigalarm_received = 0; static int sigint_received = 0; static int reconnect_sec = 1; static const char *casterouthost = NTRIP_CASTER; static char rtsp_extension[SZ] = ""; static const char *mountpoint = NULL; static int udp_cseq = 1; static int udp_tim, udp_seq, udp_init; /* Forward references */ static void send_receive_loop(sockettype sock, int outmode, struct sockaddr *pcasterRTP, socklen_t length, unsigned int rtpssrc, int chnunkymode); static void usage(int, char*); static int encode(char *buf, int size, const char *user, const char *pwd); static int send_to_caster(char *input, sockettype socket, int input_size); static void close_session(const char *caster_addr, const char *mountpoint, int session, char *rtsp_ext, int fallback); static int reconnect(int rec_sec, int rec_sec_max); static void handle_sigint(int sig); static void setup_signal_handler(int sig, void (*handler)(int)); #ifndef WINDOWSVERSION static int openserial(const char *tty, int blocksz, int baud); static void handle_sigpipe(int sig); static void handle_alarm(int sig); #else static HANDLE openserial(const char * tty, int baud); #endif /* * main * * Main entry point for the program. Processes command-line arguments and * prepares for action. * * Parameters: * argc : integer : Number of command-line arguments. * argv : array of char : Command-line arguments as an array of * zero-terminated pointers to strings. * * Return Value: * The function does not return a value (although its return type is int). * * Remarks: * */ int main(int argc, char **argv) { int c; int size = 2048; /* for setting send buffer size */ struct sockaddr_in caster; const char *proxyhost = ""; unsigned int proxyport = 0; /*** INPUT ***/ const char *casterinhost = 0; unsigned int casterinport = 0; const char *inhost = 0; unsigned int inport = 0; int chunkymode = 0; char get_extension[SZ] = ""; struct hostent *he; const char *sisnetpassword = ""; const char *sisnetuser = ""; const char *stream_name = 0; const char *stream_user = 0; const char *stream_password = 0; const char *recvrid = 0; const char *recvrpwd = 0; const char *initfile = NULL; int bindmode = 0; /*** OUTPUT ***/ unsigned int casteroutport = NTRIP_PORT; const char *outhost = 0; unsigned int outport = 0; char post_extension[SZ] = ""; const char *ntrip_str = ""; const char *user = ""; const char *password = ""; int outputmode = NTRIP1; struct sockaddr_in casterRTP; struct sockaddr_in local; int client_port = 0; int server_port = 0; unsigned int session = 0; socklen_t len = 0; int i = 0; char szSendBuffer[BUFSZ]; char authorization[SZ]; int nBufferBytes = 0; char *dlim = " \r\n="; char *token; char *tok_buf[BUFSZ]; int reconnect_sec_max = 0; setbuf(stdout, 0); setbuf(stdin, 0); setbuf(stderr, 0); { char *a; int i = 2; strcpy(revisionstr, "1."); for (a = revisionstr + 11; *a && *a != ' '; ++a) revisionstr[i++] = *a; revisionstr[i] = 0; i = 0; for (a = datestr + 7; *a && *a != ' '; ++a) datestr[i++] = *a; datestr[i] = 0; } /* setup signal handler for CTRL+C */ setup_signal_handler(SIGINT, handle_sigint); #ifndef WINDOWSVERSION /* setup signal handler for boken pipe */ setup_signal_handler(SIGPIPE, handle_sigpipe); /* setup signal handler for timeout */ setup_signal_handler(SIGALRM, handle_alarm); alarm(ALARMTIME); #else /* winsock initialization */ WSADATA wsaData; if (WSAStartup(MAKEWORD(1,1), &wsaData)) { fprintf(stderr, "Could not init network access.\n"); return 20; } #endif /* get and check program arguments */ if (argc <= 1) { usage(2, argv[0]); exit(1); } while ((c = getopt(argc, argv, "M:i:h:b:p:s:a:m:c:H:P:f:x:y:l:u:V:D:U:W:O:E:F:R:N:n:B")) != EOF) { switch (c) { case 'M': /*** InputMode ***/ if (!strcmp(optarg, "serial")) inputmode = SERIAL; else if (!strcmp(optarg, "tcpsocket")) inputmode = TCPSOCKET; else if (!strcmp(optarg, "file")) inputmode = INFILE; else if (!strcmp(optarg, "sisnet")) inputmode = SISNET; else if (!strcmp(optarg, "udpsocket")) inputmode = UDPSOCKET; else if (!strcmp(optarg, "ntrip1")) inputmode = NTRIP1_IN; else if (!strcmp(optarg, "ntrip2http")) inputmode = NTRIP2_HTTP_IN; else inputmode = atoi(optarg); if ((inputmode == 0) || (inputmode >= LAST)) { fprintf(stderr, "ERROR: can't convert <%s> to a valid InputMode\n", optarg); usage(-1, argv[0]); } break; case 'i': /* serial input device */ ttyport = optarg; break; case 'B': /* bind to incoming UDP stream */ bindmode = 1; break; case 'V': /* Sisnet data server version number */ if (!strcmp("3.0", optarg)) sisnet = 30; else if (!strcmp("3.1", optarg)) sisnet = 31; else if (!strcmp("2.1", optarg)) sisnet = 21; else { fprintf(stderr, "ERROR: unknown SISNeT version <%s>\n", optarg); usage(-2, argv[0]); } break; case 'b': /* serial input baud rate */ ttybaud = atoi(optarg); if (ttybaud <= 1) { fprintf(stderr, "ERROR: can't convert <%s> to valid serial baud rate\n", optarg); usage(1, argv[0]); } break; case 'a': /* Destination caster address */ casterouthost = optarg; break; case 'p': /* Destination caster port */ casteroutport = atoi(optarg); if (casteroutport <= 1 || casteroutport > 65535) { fprintf(stderr, "ERROR: can't convert <%s> to a valid HTTP server port\n", optarg); usage(1, argv[0]); } break; case 'm': /* Destination caster mountpoint for stream upload */ mountpoint = optarg; break; case 's': /* File name for input data simulation from file */ filepath = optarg; break; case 'f': /* name of an initialization file */ initfile = optarg; break; case 'x': /* user ID to access incoming stream */ recvrid = optarg; break; case 'y': /* password to access incoming stream */ recvrpwd = optarg; break; case 'u': /* Sisnet data server user ID */ sisnetuser = optarg; break; case 'l': /* Sisnet data server password */ sisnetpassword = optarg; break; case 'c': /* DestinationCaster password for stream upload to mountpoint */ password = optarg; break; case 'H': /* Input host address*/ casterinhost = optarg; break; case 'P': /* Input port */ casterinport = atoi(optarg); if (casterinport <= 1 || casterinport > 65535) { fprintf(stderr, "ERROR: can't convert <%s> to a valid port number\n", optarg); usage(1, argv[0]); } break; case 'D': /* Source caster mountpoint for stream input */ stream_name = optarg; break; case 'U': /* Source caster user ID for input stream access */ stream_user = optarg; break; case 'W': /* Source caster password for input stream access */ stream_password = optarg; break; case 'E': /* Proxy Server */ proxyhost = optarg; break; case 'F': /* Proxy port */ proxyport = atoi(optarg); break; case 'R': /* maximum delay between reconnect attempts in seconds */ reconnect_sec_max = atoi(optarg); break; case 'O': /* OutputMode */ outputmode = 0; if (!strcmp(optarg, "n") || !strcmp(optarg, "ntrip1")) outputmode = NTRIP1; else if (!strcmp(optarg, "h") || !strcmp(optarg, "http")) outputmode = HTTP; else if (!strcmp(optarg, "r") || !strcmp(optarg, "rtsp")) outputmode = RTSP; else if (!strcmp(optarg, "u") || !strcmp(optarg, "udp")) outputmode = UDP; else if (!strcmp(optarg, "t") || !strcmp(optarg, "tcpip")) outputmode = TCPIP; else outputmode = atoi(optarg); if ((outputmode == 0) || (outputmode >= END)) { fprintf(stderr, "ERROR: can't convert <%s> to a valid OutputMode\n", optarg); usage(-1, argv[0]); } break; case 'n': /* Destination caster user ID for stream upload to mountpoint */ user = optarg; break; case 'N': /* Ntrip-STR, optional for Ntrip Version 2.0 */ ntrip_str = optarg; break; case 'h': /* print help screen */ case '?': usage(0, argv[0]); break; default: usage(2, argv[0]); break; } } argc -= optind; argv += optind; /*** argument analysis ***/ if (argc > 0) { fprintf(stderr, "ERROR: Extra args on command line: "); for (; argc > 0; argc--) { fprintf(stderr, " %s", *argv++); } fprintf(stderr, "\n"); usage(1, argv[0]); /* never returns */ } if ((reconnect_sec_max > 0) && (reconnect_sec_max < 256)) { fprintf(stderr, "WARNING: maximum delay between reconnect attempts changed from %d to 256 seconds\n", reconnect_sec_max); reconnect_sec_max = 256; } if (!mountpoint && outputmode != TCPIP) { fprintf(stderr, "ERROR: Missing mountpoint argument for stream upload\n"); exit(1); } if (outputmode == TCPIP) { mountpoint = NULL; } if (!password[0]) { if (outputmode != TCPIP) fprintf(stderr, "WARNING: Missing password argument for stream upload - are you really sure?\n"); } else { nBufferBytes += encode(authorization, sizeof(authorization), user, password); if (nBufferBytes > (int) sizeof(authorization)) { fprintf(stderr, "ERROR: user ID and/or password too long: %d (%d)\n" " user ID: %s \npassword: <%s>\n", nBufferBytes, (int) sizeof(authorization), user, password); exit(1); } } if (stream_name && stream_user && !stream_password) { fprintf(stderr, "WARNING: Missing password argument for stream download - are you really sure?\n"); } /*** proxy server handling ***/ if (*proxyhost) { inhost = proxyhost; inport = proxyport; i = snprintf(szSendBuffer, sizeof(szSendBuffer), "http://%s:%d", casterinhost, casterinport); if ((i > SZ) || (i < 0)) { fprintf(stderr, "ERROR: Destination caster name/port to long - length = %d (max: %d)\n", i, SZ); exit(0); } else { strncpy(get_extension, szSendBuffer, (size_t) i); strcpy(szSendBuffer, ""); i = 0; } if (strstr(casterouthost, "127.0.0.1") || strstr(casterouthost, "localhost")) { outhost = casterouthost; outport = casteroutport; } else { outhost = proxyhost; outport = proxyport; i = snprintf(szSendBuffer, sizeof(szSendBuffer), "http://%s:%d", casterouthost, casteroutport); if ((i > SZ) || (i < 0)) { fprintf(stderr, "ERROR: Destination caster name/port to long - length = %d (max: %d)\n", i, SZ); exit(0); } else { strncpy(post_extension, szSendBuffer, (size_t) i); strcpy(szSendBuffer, ""); i = snprintf(szSendBuffer, sizeof(szSendBuffer), ":%d", casteroutport); strncpy(rtsp_extension, szSendBuffer, SZ); strcpy(szSendBuffer, ""); i = 0; } } } else { outhost = casterouthost; outport = casteroutport; inhost = casterinhost; inport = casterinport; } while (inputmode != LAST) { int input_init = 1; if (sigint_received) break; /*** InputMode handling ***/ switch (inputmode) { case INFILE: { if ((gps_file = open(filepath, O_RDONLY)) < 0) { perror("ERROR: opening input file"); exit(1); } #ifndef WINDOWSVERSION /* set blocking inputmode in case it was not set (seems to be sometimes for fifo's) */ fcntl(gps_file, F_SETFL, 0); #endif printf("file input: file = %s\n", filepath); } break; case SERIAL: /* open serial port */ { #ifndef WINDOWSVERSION gps_serial = openserial(ttyport, 1, ttybaud); #else gps_serial = openserial(ttyport, ttybaud); #endif if (gps_serial == INVALID_HANDLE_VALUE) exit(1); printf("serial input: device = %s, speed = %d\n", ttyport, ttybaud); if (initfile) { char buffer[1024]; FILE *fh; int i; if ((fh = fopen(initfile, "r"))) { while ((i = fread(buffer, 1, sizeof(buffer), fh)) > 0) { #ifndef WINDOWSVERSION if ((write(gps_serial, buffer, i)) != i) { perror("WARNING: sending init file"); input_init = 0; break; } #else DWORD nWrite = -1; if(!WriteFile(gps_serial, buffer, sizeof(buffer), &nWrite, NULL)) { fprintf(stderr,"ERROR: sending init file \n"); input_init = 0; break; } i = (int)nWrite; #endif } if (i < 0) { perror("ERROR: reading init file"); reconnect_sec_max = 0; input_init = 0; break; } fclose(fh); } else { fprintf(stderr, "ERROR: can't read init file <%s>\n", initfile); reconnect_sec_max = 0; input_init = 0; break; } } } break; case TCPSOCKET: case UDPSOCKET: case SISNET: case NTRIP1_IN: case NTRIP2_HTTP_IN: { if (inputmode == SISNET) { if (!inhost) inhost = SISNET_SERVER; if (!inport) inport = SISNET_PORT; } else if (inputmode == NTRIP1_IN || inputmode == NTRIP2_HTTP_IN) { if (!inport) inport = NTRIP_PORT; if (!inhost) inhost = NTRIP_CASTER; } else if ((inputmode == TCPSOCKET) || (inputmode == UDPSOCKET)) { if (!inport) inport = SERV_TCP_PORT; if (!inhost) inhost = SERV_HOST_ADDR; } if (!(he = gethostbyname(inhost))) { fprintf(stderr, "ERROR: Input host <%s> unknown\n", inhost); usage(-2, argv[0]); } if ((gps_socket = socket(AF_INET, inputmode == UDPSOCKET ? SOCK_DGRAM : SOCK_STREAM, 0)) == INVALID_SOCKET) { fprintf(stderr, "ERROR: can't create socket for incoming data stream\n"); exit(1); } memset((char*) &caster, 0x00, sizeof(caster)); if (!bindmode) memcpy(&caster.sin_addr, he->h_addr, (size_t)he->h_length); caster.sin_family = AF_INET; caster.sin_port = htons(inport); fprintf(stderr, "%s input: host = %s, port = %d, %s%s%s%s%s\n", inputmode == NTRIP1_IN ? "ntrip1" : inputmode == NTRIP2_HTTP_IN ? "ntrip2" : inputmode == SISNET ? "sisnet" : inputmode == TCPSOCKET ? "tcp socket" : "udp socket", bindmode ? "127.0.0.1" : inet_ntoa(caster.sin_addr), inport, stream_name ? "stream = " : "", stream_name ? stream_name : "", initfile ? ", initfile = " : "", initfile ? initfile : "", bindmode ? "binding mode" : ""); if (bindmode) { if (bind(gps_socket, (struct sockaddr*) &caster, sizeof(caster)) < 0) { fprintf(stderr, "ERROR: can't bind input to port %d\n", inport); reconnect_sec_max = 0; input_init = 0; break; } } /* connect to input-caster or proxy server*/ else if (connect(gps_socket, (struct sockaddr*) &caster, sizeof(caster)) < 0) { fprintf(stderr, "WARNING: can't connect input to %s at port %d\n", inet_ntoa(caster.sin_addr), inport); input_init = 0; break; } /* input from NTRIP caster */ if (stream_name) { int init = 0; /* set socket buffer size */ setsockopt(gps_socket, SOL_SOCKET, SO_SNDBUF, (const char*) &size, sizeof(const char*)); /* input from Ntrip caster*/ nBufferBytes=snprintf(szSendBuffer, sizeof(szSendBuffer) - 40,/* leave some space for login */ "GET %s/%s HTTP/1.1\r\n" "Host: %s\r\n" "%s" "User-Agent: %s/%s\r\n" //"%s%s%s" // nmea "Connection: close%s", get_extension, stream_name ? stream_name : "", casterinhost, inputmode == NTRIP1_IN ? "" : "Ntrip-Version: Ntrip/2.0\r\n", AGENTSTRING, revisionstr, //args.nmea ? "Ntrip-GGA: " : "", args.nmea ? args.nmea : "", args.nmea ? "\r\n" : "", // TODO: add argument (*stream_user || *stream_password) ? "\r\nAuthorization: Basic " : ""); /* second check for old glibc */ if (nBufferBytes > (int) sizeof(szSendBuffer) - 40 || nBufferBytes < 0) { fprintf(stderr, "ERROR: Source caster request too long\n"); input_init = 0; reconnect_sec_max = 0; break; } nBufferBytes += encode(szSendBuffer + nBufferBytes, sizeof(szSendBuffer) - nBufferBytes - 4, stream_user, stream_password); if (nBufferBytes > (int) sizeof(szSendBuffer) - 4) { fprintf(stderr, "ERROR: Source caster user ID and/or password too long\n"); input_init = 0; reconnect_sec_max = 0; break; } szSendBuffer[nBufferBytes++] = '\r'; szSendBuffer[nBufferBytes++] = '\n'; szSendBuffer[nBufferBytes++] = '\r'; szSendBuffer[nBufferBytes++] = '\n'; #ifndef NDEBUG fprintf(stdout, "%s\n", szSendBuffer); #endif if ((send(gps_socket, szSendBuffer, (size_t) nBufferBytes, 0)) != nBufferBytes) { fprintf(stderr, "WARNING: could not send Source caster request\n"); input_init = 0; break; } nBufferBytes = 0; /* check Source caster's response */ while (!init && nBufferBytes < (int) sizeof(szSendBuffer) && (nBufferBytes += recv(gps_socket, szSendBuffer, sizeof(szSendBuffer) - nBufferBytes, 0)) > 0) { if( nBufferBytes > 17 && !strstr(szSendBuffer, "ICY 200 OK") && /* case 'proxy & ntrip 1.0 caster' */ (!strncmp(szSendBuffer, "HTTP/1.1 200 OK\r\n", 17) || !strncmp(szSendBuffer, "HTTP/1.0 200 OK\r\n", 17)) ) { const char *datacheck = "Content-Type: gnss/data\r\n"; const char *chunkycheck = "Transfer-Encoding: chunked\r\n"; int l = strlen(datacheck)-1; int j=0; for(i = 0; j != l && i < nBufferBytes-l; ++i) { for(j = 0; j < l && szSendBuffer[i+j] == datacheck[j]; ++j) ; } if(i == nBufferBytes-l) { fprintf(stderr, "No 'Content-Type: gnss/data' found\n"); input_init = 0; } l = strlen(chunkycheck)-1; j=0; for(i = 0; j != l && i < nBufferBytes-l; ++i) { for(j = 0; j < l && szSendBuffer[i+j] == chunkycheck[j]; ++j) ; } if(i < nBufferBytes-l) chunkymode = 1; init = 1; } else if (strstr(szSendBuffer, "\r\n")) { if (!strstr(szSendBuffer, "ICY 200 OK")) { int k; fprintf(stderr, "ERROR: could not get requested data from Source caster: "); for (k = 0; k < nBufferBytes && szSendBuffer[k] != '\n' && szSendBuffer[k] != '\r'; ++k) { fprintf(stderr, "%c", isprint(szSendBuffer[k]) ? szSendBuffer[k] : '.'); } fprintf(stderr, "\n"); if (!strstr(szSendBuffer, "SOURCETABLE 200 OK")) { reconnect_sec_max = 0; } input_init = 0; break; } init = 1; } } } if (initfile && inputmode != SISNET) { char buffer[1024]; FILE *fh; int i; if ((fh = fopen(initfile, "r"))) { while ((i = fread(buffer, 1, sizeof(buffer), fh)) > 0) { if ((send(gps_socket, buffer, (size_t) i, 0)) != i) { perror("WARNING: sending init file"); input_init = 0; break; } } if (i < 0) { perror("ERROR: reading init file"); reconnect_sec_max = 0; input_init = 0; break; } fclose(fh); } else { fprintf(stderr, "ERROR: can't read init file <%s>\n", initfile); reconnect_sec_max = 0; input_init = 0; break; } } } if (inputmode == SISNET) { int i, j; char buffer[1024]; i = snprintf(buffer, sizeof(buffer), sisnet >= 30 ? "AUTH,%s,%s\r\n" : "AUTH,%s,%s", sisnetuser, sisnetpassword); if ((send(gps_socket, buffer, (size_t) i, 0)) != i) { perror("WARNING: sending authentication for SISNeT data server"); input_init = 0; break; } i = sisnet >= 30 ? 7 : 5; if ((j = recv(gps_socket, buffer, i, 0)) != i && strncmp("*AUTH", buffer, 5)) { fprintf(stderr, "WARNING: SISNeT connect failed:"); for (i = 0; i < j; ++i) { if (buffer[i] != '\r' && buffer[i] != '\n') { fprintf(stderr, "%c", isprint(buffer[i]) ? buffer[i] : '.'); } } fprintf(stderr, "\n"); input_init = 0; break; } if (sisnet >= 31) { if ((send(gps_socket, "START\r\n", 7, 0)) != i) { perror("WARNING: sending Sisnet start command"); input_init = 0; break; } } } /*** receiver authentication ***/ if (recvrid && recvrpwd && ((inputmode == TCPSOCKET) || (inputmode == UDPSOCKET))) { if (strlen(recvrid) > (BUFSZ - 3)) { fprintf(stderr, "ERROR: Receiver ID too long\n"); reconnect_sec_max = 0; input_init = 0; break; } else { fprintf(stderr, "Sending user ID for receiver...\n"); nBufferBytes = recv(gps_socket, szSendBuffer, BUFSZ, 0); strcpy(szSendBuffer, recvrid); strcat(szSendBuffer, "\r\n"); if (send(gps_socket, szSendBuffer, strlen(szSendBuffer), MSG_DONTWAIT) < 0) { perror("WARNING: sending user ID for receiver"); input_init = 0; break; } } if (strlen(recvrpwd) > (BUFSZ - 3)) { fprintf(stderr, "ERROR: Receiver password too long\n"); reconnect_sec_max = 0; input_init = 0; break; } else { fprintf(stderr, "Sending user password for receiver...\n"); nBufferBytes = recv(gps_socket, szSendBuffer, BUFSZ, 0); strcpy(szSendBuffer, recvrpwd); strcat(szSendBuffer, "\r\n"); if (send(gps_socket, szSendBuffer, strlen(szSendBuffer), MSG_DONTWAIT) < 0) { perror("WARNING: sending user password for receiver"); input_init = 0; break; } } } break; default: usage(-1, argv[0]); break; } /* ----- main part ----- */ int output_init = 1, fallback = 0; while ((input_init) && (output_init)) { #ifndef WINDOWSVERSION if ((sigalarm_received) || (sigint_received) || (sigpipe_received)) break; #else if((sigalarm_received) || (sigint_received)) break; #endif if (!(he = gethostbyname(outhost))) { fprintf(stderr, "ERROR: Destination caster, server or proxy host <%s> unknown\n", outhost); close_session(casterouthost, mountpoint, session, rtsp_extension, 0); usage(-2, argv[0]); } else { fprintf(stderr, "Destination caster, server or proxy host <%s> \n", outhost);} /* create socket */ if ((socket_tcp = socket(AF_INET, (outputmode == UDP ? SOCK_DGRAM : SOCK_STREAM), 0)) == INVALID_SOCKET) { perror("ERROR: tcp socket"); reconnect_sec_max = 0; break; } if (outputmode == TCPIP) { // Forcefully attaching socket to the local port int opt = 1; if (setsockopt(socket_tcp, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) { perror("ERROR: setsockopt"); break; } } memset((char*) &caster, 0x00, sizeof(caster)); memcpy(&caster.sin_addr, he->h_addr, (size_t)he->h_length); caster.sin_family = AF_INET; caster.sin_port = htons(outport); /* connect to Destination caster, server or proxy host */ fprintf(stderr, "caster|server output: host = %s, port = %d, mountpoint = %s" ", mode = %s\n\n", inet_ntoa(caster.sin_addr), outport, mountpoint, outputmode == NTRIP1 ? "ntrip1" : outputmode == HTTP ? "http" : outputmode == UDP ? "udp" : outputmode == RTSP ? "rtsp" : "tcpip"); if (outputmode == TCPIP) { caster.sin_addr.s_addr = INADDR_ANY; // Forcefully attaching socket to the local port if (bind(socket_tcp, (struct sockaddr *)&caster, sizeof(caster)) < 0) { perror("ERROR: bind failed"); reconnect_sec_max = 0; output_init = 0; break; } if (listen(socket_tcp, 3) < 0) { perror("listen"); reconnect_sec_max = 0; output_init = 0; break; } int addrlen = sizeof(caster); if ((local_socket_tcp = accept(socket_tcp, (struct sockaddr *)&caster, (socklen_t*)&addrlen)) < 0) { perror("ERROR: accept"); reconnect_sec_max = 0; output_init = 0; break; } } else { if (connect(socket_tcp, (struct sockaddr*) &caster, sizeof(caster)) < 0) { fprintf(stderr, "WARNING: can't connect output to %s at port %d", inet_ntoa(caster.sin_addr), outport); break; } } /*** OutputMode handling ***/ switch (outputmode) { case UDP: { unsigned int session; char rtpbuf[1526]; int i = 12, j; udp_init = time(0); srand(udp_init); session = rand(); udp_tim = rand(); udp_seq = rand(); rtpbuf[0] = (2 << 6); /* padding, extension, csrc are empty */ rtpbuf[1] = 97; /* marker is empty */ rtpbuf[2] = (udp_seq >> 8) & 0xFF; rtpbuf[3] = (udp_seq) & 0xFF; rtpbuf[4] = (udp_tim >> 24) & 0xFF; rtpbuf[5] = (udp_tim >> 16) & 0xFF; rtpbuf[6] = (udp_tim >> 8) & 0xFF; rtpbuf[7] = (udp_tim) & 0xFF; /* sequence and timestamp are empty */ rtpbuf[8] = (session >> 24) & 0xFF; rtpbuf[9] = (session >> 16) & 0xFF; rtpbuf[10] = (session >> 8) & 0xFF; rtpbuf[11] = (session) & 0xFF; ++udp_seq; j = snprintf(rtpbuf + i, sizeof(rtpbuf) - i - 40, /* leave some space for login */ "POST /%s HTTP/1.1\r\n" "Host: %s\r\n" "Ntrip-Version: Ntrip/2.0\r\n" "User-Agent: %s/%s\r\n" "Authorization: Basic %s%s%s\r\n" "Connection: close\r\n" "Transfer-Encoding: chunked\r\n\r\n", mountpoint, casterouthost, AGENTSTRING, revisionstr, authorization, ntrip_str ? (outputmode == NTRIP1 ? "\r\nSTR: " : "\r\nNtrip-STR: ") : "", ntrip_str); i += j; if (i > (int) sizeof(rtpbuf) - 40 || j < 0) /* second check for old glibc */ { fprintf(stderr, "Requested data too long\n"); reconnect_sec_max = 0; output_init = 0; break; } else { rtpbuf[i++] = '\r'; rtpbuf[i++] = '\n'; rtpbuf[i++] = '\r'; rtpbuf[i++] = '\n'; if (send(socket_tcp, rtpbuf, i, 0) != i) { perror("Could not send UDP packet"); reconnect_sec_max = 0; output_init = 0; break; } else { int stop = 0; int numbytes; if ((numbytes = recv(socket_tcp, rtpbuf, sizeof(rtpbuf) - 1, 0)) > 0) { /* we don't expect message longer than 1513, so we cut the last byte for security reasons to prevent buffer overrun */ rtpbuf[numbytes] = 0; if (numbytes > 17 + 12 && (!strncmp(rtpbuf + 12, "HTTP/1.1 200 OK\r\n", 17) || !strncmp(rtpbuf + 12, "HTTP/1.0 200 OK\r\n", 17))) { const char *sessioncheck = "session: "; int l = strlen(sessioncheck) - 1; int j = 0; for (i = 12; j != l && i < numbytes - l; ++i) { for (j = 0; j < l && tolower(rtpbuf[i + j]) == sessioncheck[j]; ++j) ; } if (i != numbytes - l) /* found a session number */ { i += l; session = 0; while (i < numbytes && rtpbuf[i] >= '0' && rtpbuf[i] <= '9') session = session * 10 + rtpbuf[i++] - '0'; if (rtpbuf[i] != '\r') { fprintf(stderr, "Could not extract session number\n"); stop = 1; } } } else { int k; fprintf(stderr, "Could not access mountpoint: "); for (k = 12; k < numbytes && rtpbuf[k] != '\n' && rtpbuf[k] != '\r'; ++k) { fprintf(stderr, "%c", isprint(rtpbuf[k]) ? rtpbuf[k] : '.'); } fprintf(stderr, "\n"); stop = 1; } } if (!stop) { send_receive_loop(socket_tcp, outputmode, NULL, 0, session, chunkymode); input_init = output_init = 0; /* send connection close always to allow nice session closing */ udp_tim += (time(0) - udp_init) * 1000000 / TIME_RESOLUTION; rtpbuf[0] = (2 << 6); /* padding, extension, csrc are empty */ rtpbuf[1] = 98; /* marker is empty */ rtpbuf[2] = (udp_seq >> 8) & 0xFF; rtpbuf[3] = (udp_seq) & 0xFF; rtpbuf[4] = (udp_tim >> 24) & 0xFF; rtpbuf[5] = (udp_tim >> 16) & 0xFF; rtpbuf[6] = (udp_tim >> 8) & 0xFF; rtpbuf[7] = (udp_tim) & 0xFF; /* sequence and timestamp are empty */ rtpbuf[8] = (session >> 24) & 0xFF; rtpbuf[9] = (session >> 16) & 0xFF; rtpbuf[10] = (session >> 8) & 0xFF; rtpbuf[11] = (session) & 0xFF; send(socket_tcp, rtpbuf, 12, 0); /* cleanup */ } else { reconnect_sec_max = 600; output_init = 0; } } } } break; case NTRIP1: /*** OutputMode Ntrip Version 1.0 ***/ fallback = 0; nBufferBytes = snprintf(szSendBuffer, sizeof(szSendBuffer), "SOURCE %s %s/%s\r\n" "Source-Agent: %s/%s\r\n\r\n", password, post_extension, mountpoint, AGENTSTRING, revisionstr); if ((nBufferBytes > (int) sizeof(szSendBuffer)) || (nBufferBytes < 0)) { fprintf(stderr, "ERROR: Destination caster request to long\n"); reconnect_sec_max = 0; output_init = 0; break; } if (!send_to_caster(szSendBuffer, socket_tcp, nBufferBytes)) { output_init = 0; break; } /* check Destination caster's response */ nBufferBytes = recv(socket_tcp, szSendBuffer, sizeof(szSendBuffer), 0); szSendBuffer[nBufferBytes] = '\0'; if (!strstr(szSendBuffer, "OK")) { char *a; fprintf(stderr, "ERROR: Destination caster's or Proxy's reply is not OK: "); for (a = szSendBuffer; *a && *a != '\n' && *a != '\r'; ++a) { fprintf(stderr, "%.1s", isprint(*a) ? a : "."); } fprintf(stderr, "\n"); if ((strstr(szSendBuffer, "ERROR - Bad Password")) || (strstr(szSendBuffer, "400 Bad Request"))) reconnect_sec_max = 0; output_init = 0; break; } #ifndef NDEBUG else { fprintf(stderr, "Destination caster response:\n%s\n", szSendBuffer); } #endif send_receive_loop(socket_tcp, outputmode, NULL, 0, 0, chunkymode); input_init = output_init = 0; break; case HTTP: /*** Ntrip-Version 2.0 HTTP/1.1 ***/ nBufferBytes = snprintf(szSendBuffer, sizeof(szSendBuffer), "POST %s/%s HTTP/1.1\r\n" "Host: %s\r\n" "Ntrip-Version: Ntrip/2.0\r\n" "User-Agent: %s/%s\r\n" "Authorization: Basic %s%s%s\r\n" "Connection: close\r\n" "Transfer-Encoding: chunked\r\n\r\n", post_extension, mountpoint, casterouthost, AGENTSTRING, revisionstr, authorization, ntrip_str ? "\r\nNtrip-STR: " : "", ntrip_str); if ((nBufferBytes > (int) sizeof(szSendBuffer)) || (nBufferBytes < 0)) { fprintf(stderr, "ERROR: Destination caster request to long\n"); reconnect_sec_max = 0; output_init = 0; break; } if (!send_to_caster(szSendBuffer, socket_tcp, nBufferBytes)) { output_init = 0; break; } /* check Destination caster's response */ nBufferBytes = recv(socket_tcp, szSendBuffer, sizeof(szSendBuffer), 0); szSendBuffer[nBufferBytes] = '\0'; if (!strstr(szSendBuffer, "HTTP/1.1 200 OK")) { char *a; fprintf(stderr, "ERROR: Destination caster's%s reply is not OK: ", *proxyhost ? " or Proxy's" : ""); for (a = szSendBuffer; *a && *a != '\n' && *a != '\r'; ++a) { fprintf(stderr, "%.1s", isprint(*a) ? a : "."); } fprintf(stderr, "\n"); /* fallback if necessary */ if (!strstr(szSendBuffer, "Ntrip-Version: Ntrip/2.0\r\n")) { fprintf(stderr, " Ntrip Version 2.0 not implemented at Destination caster" " <%s>%s%s%s\n%s\n" "ntripserver falls back to Ntrip Version 1.0\n\n", casterouthost, *proxyhost ? " or Proxy <" : "", proxyhost, *proxyhost ? ">" : "", *proxyhost ? " or HTTP/1.1 not implemented at Proxy\n" : ""); close_session(casterouthost, mountpoint, session, rtsp_extension, 1); outputmode = NTRIP1; break; } else if ((strstr(szSendBuffer, "HTTP/1.1 401 Unauthorized")) || (strstr(szSendBuffer, "501 Not Implemented"))) { reconnect_sec_max = 0; } output_init = 0; break; } #ifndef NDEBUG else { fprintf(stderr, "Destination caster response:\n%s\n", szSendBuffer); } #endif send_receive_loop(socket_tcp, outputmode, NULL, 0, 0, chunkymode); input_init = output_init = 0; break; case RTSP: /*** Ntrip-Version 2.0 RTSP / RTP ***/ if ((socket_udp = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) { perror("ERROR: udp socket"); exit(4); } /* fill structure with local address information for UDP */ memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(0); local.sin_addr.s_addr = htonl(INADDR_ANY); len = (socklen_t) sizeof(local); /* bind() in order to get a random RTP client_port */ if ((bind(socket_udp, (struct sockaddr*) &local, len)) < 0) { perror("ERROR: udp bind"); reconnect_sec_max = 0; output_init = 0; break; } if ((getsockname(socket_udp, (struct sockaddr*) &local, &len)) != -1) { client_port = (unsigned int) ntohs(local.sin_port); } else { perror("ERROR: getsockname(localhost)"); reconnect_sec_max = 0; output_init = 0; break; } nBufferBytes = snprintf(szSendBuffer, sizeof(szSendBuffer), "SETUP rtsp://%s%s/%s RTSP/1.0\r\n" "CSeq: %d\r\n" "Ntrip-Version: Ntrip/2.0\r\n" "Ntrip-Component: Ntripserver\r\n" "User-Agent: %s/%s\r\n" "Transport: RTP/GNSS;unicast;client_port=%u\r\n" "Authorization: Basic %s%s%s\r\n\r\n", casterouthost, rtsp_extension, mountpoint, udp_cseq++, AGENTSTRING, revisionstr, client_port, authorization, ntrip_str ? "\r\nNtrip-STR: " : "", ntrip_str); if ((nBufferBytes > (int) sizeof(szSendBuffer)) || (nBufferBytes < 0)) { fprintf(stderr, "ERROR: Destination caster request to long\n"); reconnect_sec_max = 0; output_init = 0; break; } if (!send_to_caster(szSendBuffer, socket_tcp, nBufferBytes)) { output_init = 0; break; } while ((nBufferBytes = recv(socket_tcp, szSendBuffer, sizeof(szSendBuffer), 0)) > 0) { /* check Destination caster's response */ szSendBuffer[nBufferBytes] = '\0'; if (!strstr(szSendBuffer, "RTSP/1.0 200 OK")) { char *a; fprintf(stderr, "ERROR: Destination caster's%s reply is not OK: ", *proxyhost ? " or Proxy's" : ""); for (a = szSendBuffer; *a && *a != '\n' && *a != '\r'; ++a) { fprintf(stderr, "%c", isprint(*a) ? *a : '.'); } fprintf(stderr, "\n"); /* fallback if necessary */ if (strncmp(szSendBuffer, "RTSP", 4) != 0) { if (strstr(szSendBuffer, "Ntrip-Version: Ntrip/2.0\r\n")) { fprintf(stderr, " RTSP not implemented at Destination caster <%s>%s%s%s\n\n" "ntripserver falls back to Ntrip Version 2.0 in TCP/IP" " mode\n\n", casterouthost, *proxyhost ? " or Proxy <" : "", proxyhost, *proxyhost ? ">" : ""); close_session(casterouthost, mountpoint, session, rtsp_extension, 1); outputmode = HTTP; fallback = 1; break; } else { fprintf(stderr, " Ntrip-Version 2.0 not implemented at Destination caster" "<%s>%s%s%s\n%s" " or RTSP/1.0 not implemented at Destination caster%s\n\n" "ntripserver falls back to Ntrip Version 1.0\n\n", casterouthost, *proxyhost ? " or Proxy <" : "", proxyhost, *proxyhost ? ">" : "", *proxyhost ? " or HTTP/1.1 not implemented at Proxy\n" : "", *proxyhost ? " or Proxy" : ""); close_session(casterouthost, mountpoint, session, rtsp_extension, 1); outputmode = NTRIP1; fallback = 1; break; } } else if ((strstr(szSendBuffer, "RTSP/1.0 401 Unauthorized")) || (strstr(szSendBuffer, "RTSP/1.0 501 Not Implemented"))) { reconnect_sec_max = 0; } output_init = 0; break; } #ifndef NDEBUG else { fprintf(stderr, "Destination caster response:\n%s\n", szSendBuffer); } #endif if ((strstr(szSendBuffer, "RTSP/1.0 200 OK\r\n")) && (strstr(szSendBuffer, "CSeq: 1\r\n"))) { for (token = strtok(szSendBuffer, dlim); token != NULL; token = strtok(NULL, dlim)) { tok_buf[i] = token; i++; } session = atoi(tok_buf[6]); server_port = atoi(tok_buf[10]); nBufferBytes = snprintf(szSendBuffer, sizeof(szSendBuffer), "RECORD rtsp://%s%s/%s RTSP/1.0\r\n" "CSeq: %d\r\n" "Session: %u\r\n" "\r\n", casterouthost, rtsp_extension, mountpoint, udp_cseq++, session); if ((nBufferBytes >= (int) sizeof(szSendBuffer)) || (nBufferBytes < 0)) { fprintf(stderr, "ERROR: Destination caster request to long\n"); reconnect_sec_max = 0; output_init = 0; break; } if (!send_to_caster(szSendBuffer, socket_tcp, nBufferBytes)) { output_init = 0; break; } } else if ((strstr(szSendBuffer, "RTSP/1.0 200 OK\r\n")) && (strstr(szSendBuffer, "CSeq: 2\r\n"))) { /* fill structure with caster address information for UDP */ memset(&casterRTP, 0, sizeof(casterRTP)); casterRTP.sin_family = AF_INET; casterRTP.sin_port = htons(((uint16_t) server_port)); if ((he = gethostbyname(outhost)) == NULL) { fprintf(stderr, "ERROR: Destination caster unknown\n"); reconnect_sec_max = 0; output_init = 0; break; } else { memcpy((char*) &casterRTP.sin_addr.s_addr, he->h_addr_list[0], (size_t) he->h_length); } len = (socklen_t) sizeof(casterRTP); send_receive_loop(socket_udp, outputmode, (struct sockaddr*) &casterRTP, (socklen_t) len, session, chunkymode); break; } else { break; } } input_init = output_init = 0; break; case TCPIP: fallback = 0; send_receive_loop(local_socket_tcp, outputmode, NULL, 0, 0, chunkymode); input_init = output_init = 0; break; } } close_session(casterouthost, mountpoint, session, rtsp_extension, 0); if ((reconnect_sec_max || fallback) && !sigint_received) reconnect_sec = reconnect(reconnect_sec, reconnect_sec_max); else inputmode = LAST; } return 0; } static void send_receive_loop(sockettype sock, int outmode, struct sockaddr *pcasterRTP, socklen_t length, unsigned int rtpssrc, int chunkymode) { int nodata = 0; char buffer[BUFSZ] = { 0 }; char sisnetbackbuffer[200]; char szSendBuffer[BUFSZ] = ""; int nBufferBytes = 0; int remainChunk = 0; /* RTSP / RTP Mode */ int isfirstpacket = 1; struct timeval now; struct timeval last = { 0, 0 }; long int sendtimediff; int rtpseq = 0; int rtptime = 0; time_t laststate = time(0); if (outmode == UDP) { rtptime = time(0); #ifdef WINDOWSVERSION u_long blockmode = 1; if(ioctlsocket(socket_tcp, FIONBIO, &blockmode)) #else /* WINDOWSVERSION */ if (fcntl(socket_tcp, F_SETFL, O_NONBLOCK) < 0) #endif /* WINDOWSVERSION */ { fprintf(stderr, "Could not set nonblocking mode\n"); return; } } else if (outmode == RTSP) { #ifdef WINDOWSVERSION u_long blockmode = 1; if(ioctlsocket(socket_tcp, FIONBIO, &blockmode)) #else /* WINDOWSVERSION */ if (fcntl(socket_tcp, F_SETFL, O_NONBLOCK) < 0) #endif /* WINDOWSVERSION */ { fprintf(stderr, "Could not set nonblocking mode\n"); return; } } /* data transmission */ fprintf(stderr, "transfering data ...\n"); int send_recv_success = 0; #ifdef WINDOWSVERSION time_t nodata_begin = 0, nodata_current = 0; #endif while (1) { if (send_recv_success < 3) send_recv_success++; if (!nodata) { #ifndef WINDOWSVERSION alarm(ALARMTIME); #else time(&nodata_begin); #endif } else { nodata = 0; #ifdef WINDOWSVERSION time(&nodata_current); if(difftime(nodata_current, nodata_begin) >= ALARMTIME) { sigalarm_received = 1; fprintf(stderr, "ERROR: more than %d seconds no activity\n", ALARMTIME); } #endif } /* signal handling*/ #ifdef WINDOWSVERSION if((sigalarm_received) || (sigint_received)) break; #else if ((sigalarm_received) || (sigint_received) || (sigpipe_received)) break; #endif if (!nBufferBytes) { if (inputmode == SISNET && sisnet <= 30) { int i; /* a somewhat higher rate than 1 second to get really each block */ /* means we need to skip double blocks sometimes */ struct timeval tv = { 0, 700000 }; select(0, 0, 0, 0, &tv); memcpy(sisnetbackbuffer, buffer, sizeof(sisnetbackbuffer)); i = (sisnet >= 30 ? 5 : 3); if ((send(gps_socket, "MSG\r\n", i, 0)) != i) { perror("WARNING: sending SISNeT data request failed"); return; } } /***********************/ /* receiving data */ /***********************/ /* INFILE */ if (inputmode == INFILE) nBufferBytes = read(gps_file, buffer, sizeof(buffer)); /* SERIAL */ else if (inputmode == SERIAL) { #ifndef WINDOWSVERSION nBufferBytes = read(gps_serial, buffer, sizeof(buffer)); #else DWORD nRead = 0; if(!ReadFile(gps_serial, buffer, sizeof(buffer), &nRead, NULL)) { fprintf(stderr,"ERROR: reading serial input failed\n"); return; } nBufferBytes = (int)nRead; #endif } /* ALL OTHER MODES */ else #ifdef WINDOWSVERSION nBufferBytes = recv(gps_socket, buffer, sizeof(buffer), 0); #else nBufferBytes = read(gps_socket, buffer, sizeof(buffer)); #endif if (!nBufferBytes) { fprintf(stderr, "WARNING: no data received from input\n"); nodata = 1; #ifndef WINDOWSVERSION sleep(3); #else Sleep(3*1000); #endif continue; } else if ((nBufferBytes < 0) && (!sigint_received)) { perror("WARNING: reading input failed"); return; } /* we can compare the whole buffer, as the additional bytes remain unchanged */ if (inputmode == SISNET && sisnet <= 30 && !memcmp(sisnetbackbuffer, buffer, sizeof(sisnetbackbuffer))) { nBufferBytes = 0; } } if (nBufferBytes < 0) return; if (chunkymode) { int cstop = 0; int pos = 0; int totalbytes = 0; static int chunksize = 0; static long i = 0; char chunkBytes[BUFSZ] = { 0 }; while (!sigint_received && !cstop && pos < nBufferBytes) { switch (chunkymode) { case 1: /* reading number starts */ chunksize = 0; ++chunkymode; /* no break */ break; case 2: /* during reading number */ i = buffer[pos++]; if (i >= '0' && i <= '9') chunksize = chunksize * 16 + i - '0'; else if (i >= 'a' && i <= 'f') chunksize = chunksize * 16 + i - 'a' + 10; else if (i >= 'A' && i <= 'F') chunksize = chunksize * 16 + i - 'A' + 10; else if (i == '\r') ++chunkymode; else if (i == ';') chunkymode = 5; else cstop = 1; break; case 3: /* scanning for return */ if (buffer[pos++] == '\n') chunkymode = chunksize ? 4 : 1; else cstop = 1; break; case 4: /* output data */ i = nBufferBytes - pos; if (i > chunksize) { i = chunksize; } memcpy(chunkBytes + totalbytes, buffer + pos, (size_t) i); totalbytes += i; chunksize -= i; pos += i; if (!chunksize) chunkymode = 1; break; case 5: if (i == '\r') chunkymode = 3; break; } } if (cstop) { fprintf(stderr, "Error in chunky transfer encoding\n"); return; } else { memset((char*) &buffer, 0x00, sizeof(buffer)); memcpy(buffer, chunkBytes, (size_t) totalbytes); nBufferBytes = totalbytes; } } /*****************/ /* send data */ /*****************/ if ((nBufferBytes) && (outmode == NTRIP1 || outmode == TCPIP)) { int i; if ((i = send(sock, buffer, (size_t) nBufferBytes, MSG_DONTWAIT)) != nBufferBytes) { if (i < 0) { if (errno != EAGAIN) { perror("WARNING: could not send data to Destination caster or localhost"); return; } } else if (i) { memmove(buffer, buffer + i, (size_t) (nBufferBytes - i)); nBufferBytes -= i; } } else { nBufferBytes = 0; } } else if ((nBufferBytes) && (outmode == UDP)) { int i; char rtpbuf[1592]; while(nBufferBytes) { int ct = time(0); int s = nBufferBytes; if(s > 1400) s = 1400; udp_tim += (ct - udp_init) * 1000000 / TIME_RESOLUTION; udp_init = ct; rtpbuf[0] = (2 << 6); rtpbuf[1] = 96; rtpbuf[2] = (udp_seq >> 8) & 0xFF; rtpbuf[3] = (udp_seq) & 0xFF; rtpbuf[4] = (udp_tim >> 24) & 0xFF; rtpbuf[5] = (udp_tim >> 16) & 0xFF; rtpbuf[6] = (udp_tim >> 8) & 0xFF; rtpbuf[7] = (udp_tim) & 0xFF; rtpbuf[8] = (rtpssrc >> 24) & 0xFF; rtpbuf[9] = (rtpssrc >> 16) & 0xFF; rtpbuf[10] = (rtpssrc >> 8) & 0xFF; rtpbuf[11] = (rtpssrc) & 0xFF; ++udp_seq; memcpy(rtpbuf + 12, buffer, s); if ((i = send(socket_tcp, rtpbuf, (size_t) s + 12, MSG_DONTWAIT)) != s + 12) { if (errno != EAGAIN) { perror("WARNING: could not send data to Destination caster"); return; } } else nBufferBytes -= s; } i = recv(socket_tcp, rtpbuf, sizeof(rtpbuf), 0); if (i >= 12 && (unsigned char) rtpbuf[0] == (2 << 6) && rtpssrc == (unsigned int) (((unsigned char) rtpbuf[8] << 24) + ((unsigned char) rtpbuf[9] << 16) + ((unsigned char) rtpbuf[10] << 8) + (unsigned char) rtpbuf[11])) { if (rtpbuf[1] == 96) rtptime = time(0); else if (rtpbuf[1] == 98) { fprintf(stderr, "Connection end\n"); return; } } else if (time(0) > rtptime + 60) { fprintf(stderr, "Timeout\n"); return; } } /*** Ntrip-Version 2.0 HTTP/1.1 ***/ else if ((nBufferBytes) && (outmode == HTTP)) { if (!remainChunk) { int nChunkBytes = snprintf(szSendBuffer, sizeof(szSendBuffer), "%x\r\n", nBufferBytes); send(sock, szSendBuffer, nChunkBytes, 0); remainChunk = nBufferBytes; } int i = send(sock, buffer, (size_t) remainChunk, MSG_DONTWAIT); if (i < 0) { if (errno != EAGAIN) { perror("WARNING: could not send data to Destination caster"); return; } } else if (i) { memmove(buffer, buffer + i, (size_t) (nBufferBytes - i)); nBufferBytes -= i; remainChunk -= i; } else { nBufferBytes = 0; remainChunk = 0; } if (!remainChunk) send(sock, "\r\n", strlen("\r\n"), 0); } /*** Ntrip-Version 2.0 RTSP(TCP) / RTP(UDP) ***/ else if ((nBufferBytes) && (outmode == RTSP)) { time_t ct; int r; char rtpbuffer[BUFSZ + 12]; int i, j; gettimeofday(&now, NULL); /* RTP data packet generation*/ if (isfirstpacket) { rtpseq = rand(); rtptime = rand(); last = now; isfirstpacket = 0; } else { ++rtpseq; sendtimediff = (((now.tv_sec - last.tv_sec) * 1000000) + (now.tv_usec - last.tv_usec)); rtptime += sendtimediff / TIME_RESOLUTION; } rtpbuffer[0] = (RTP_VERSION << 6); /* padding, extension, csrc are empty */ rtpbuffer[1] = 96; /* marker is empty */ rtpbuffer[2] = rtpseq >> 8; rtpbuffer[3] = rtpseq; rtpbuffer[4] = rtptime >> 24; rtpbuffer[5] = rtptime >> 16; rtpbuffer[6] = rtptime >> 8; rtpbuffer[7] = rtptime; rtpbuffer[8] = rtpssrc >> 24; rtpbuffer[9] = rtpssrc >> 16; rtpbuffer[10] = rtpssrc >> 8; rtpbuffer[11] = rtpssrc; for (j = 0; j < nBufferBytes; j++) { rtpbuffer[12 + j] = buffer[j]; } last.tv_sec = now.tv_sec; last.tv_usec = now.tv_usec; if ((i = sendto(sock, rtpbuffer, 12 + nBufferBytes, 0, pcasterRTP, length)) != (nBufferBytes + 12)) { if (i < 0) { if (errno != EAGAIN) { perror("WARNING: could not send data to Destination caster"); return; } } else if (i) { memmove(buffer, buffer + (i - 12), (size_t) (nBufferBytes - (i - 12))); nBufferBytes -= i - 12; } } else { nBufferBytes = 0; } ct = time(0); if (ct - laststate > 15) { i = snprintf(buffer, sizeof(buffer), "GET_PARAMETER rtsp://%s%s/%s RTSP/1.0\r\n" "CSeq: %d\r\n" "Session: %u\r\n" "\r\n", casterouthost, rtsp_extension, mountpoint, udp_cseq++, rtpssrc); if (i > (int) sizeof(buffer) || i < 0) { fprintf(stderr, "Requested data too long\n"); return; } else if (send(socket_tcp, buffer, (size_t) i, 0) != i) { perror("send"); return; } laststate = ct; } /* ignore RTSP server replies */ if ((r = recv(socket_tcp, buffer, sizeof(buffer), 0)) < 0) { #ifdef WINDOWSVERSION if(WSAGetLastError() != WSAEWOULDBLOCK) #else /* WINDOWSVERSION */ if (errno != EAGAIN) #endif /* WINDOWSVERSION */ { fprintf(stderr, "Control connection closed\n"); return; } } else if (!r) { fprintf(stderr, "Control connection read error\n"); return; } } if (send_recv_success == 3) reconnect_sec = 1; } return; } /******************************************************************** * openserial * * Open the serial port with the given device name and configure it for * reading NMEA data from a GPS receiver. * * Parameters: * tty : pointer to : A zero-terminated string containing the device * unsigned char name of the appropriate serial port. * blocksz : integer : Block size for port I/O (ifndef WINDOWSVERSION) * baud : integer : Baud rate for port I/O * * Return Value: * The function returns a file descriptor for the opened port if successful. * The function returns -1 / INVALID_HANDLE_VALUE in the event of an error. * * Remarks: * ********************************************************************/ #ifndef WINDOWSVERSION static int openserial(const char *tty, int blocksz, int baud) { struct termios termios; /*** opening the serial port ***/ gps_serial = open(tty, O_RDWR | O_NONBLOCK | O_EXLOCK); if (gps_serial < 0) { perror("ERROR: opening serial connection"); return (-1); } /*** configuring the serial port ***/ if (tcgetattr(gps_serial, &termios) < 0) { perror("ERROR: get serial attributes"); return (-1); } termios.c_iflag = 0; termios.c_oflag = 0; /* (ONLRET) */ termios.c_cflag = CS8 | CLOCAL | CREAD; termios.c_lflag = 0; { int cnt; for (cnt = 0; cnt < NCCS; cnt++) termios.c_cc[cnt] = -1; } termios.c_cc[VMIN] = blocksz; termios.c_cc[VTIME] = 2; #if (B4800 != 4800) /* Not every system has speed settings equal to absolute speed value. */ switch (baud) { case 300: baud = B300; break; case 1200: baud = B1200; break; case 2400: baud = B2400; break; case 4800: baud = B4800; break; case 9600: baud = B9600; break; case 19200: baud = B19200; break; case 38400: baud = B38400; break; #ifdef B57600 case 57600: baud = B57600; break; #endif #ifdef B115200 case 115200: baud = B115200; break; #endif #ifdef B230400 case 230400: baud = B230400; break; #endif default: fprintf(stderr, "WARNING: Baud settings not useful, using 19200\n"); baud = B19200; break; } #endif if (cfsetispeed(&termios, baud) != 0) { perror("ERROR: setting serial speed with cfsetispeed"); return (-1); } if (cfsetospeed(&termios, baud) != 0) { perror("ERROR: setting serial speed with cfsetospeed"); return (-1); } if (tcsetattr(gps_serial, TCSANOW, &termios) < 0) { perror("ERROR: setting serial attributes"); return (-1); } if (fcntl(gps_serial, F_SETFL, 0) == -1) { perror("WARNING: setting blocking inputmode failed"); } return (gps_serial); } #else static HANDLE openserial(const char * tty, int baud) { char compath[15] = ""; snprintf(compath, sizeof(compath), "\\\\.\\%s", tty); if((gps_serial = CreateFile(compath, GENERIC_WRITE|GENERIC_READ , 0, 0, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE) { fprintf(stderr, "ERROR: opening serial connection\n"); return (INVALID_HANDLE_VALUE); } DCB dcb; memset(&dcb, 0, sizeof(dcb)); char str[100]; snprintf(str,sizeof(str), "baud=%d parity=N data=8 stop=1 xon=off octs=off rts=off", baud); COMMTIMEOUTS ct = {1000, 1, 0, 0, 0}; if(!BuildCommDCB(str, &dcb)) { fprintf(stderr, "ERROR: get serial attributes\n"); return (INVALID_HANDLE_VALUE); } else if(!SetCommState(gps_serial, &dcb)) { fprintf(stderr, "ERROR: set serial attributes\n"); return (INVALID_HANDLE_VALUE); } else if(!SetCommTimeouts(gps_serial, &ct)) { fprintf(stderr, "ERROR: set serial timeouts\n"); return (INVALID_HANDLE_VALUE); } return (gps_serial); } #endif /******************************************************************** * usage * * Send a usage message to standard error and quit the program. * * Parameters: * None. * * Return Value: * The function does not return a value. * * Remarks: * *********************************************************************/ #ifdef __GNUC__ __attribute__ ((noreturn)) #endif /* __GNUC__ */ void usage(int rc, char *name) { fprintf(stderr, "Version %s (%s) GPL" COMPILEDATE "\nUsage:\n%s [OPTIONS]\n", revisionstr, datestr, name); fprintf(stderr, "PURPOSE\n"); fprintf(stderr, " The purpose of this program is to pick up a GNSS data stream (Input, Source)\n"); fprintf(stderr, " from either\n\n"); fprintf(stderr, " 1. a Serial port, or\n"); fprintf(stderr, " 2. an IP server, or\n"); fprintf(stderr, " 3. a File, or\n"); fprintf(stderr, " 4. a SISNeT Data Server, or\n"); fprintf(stderr, " 5. a UDP server, or\n"); fprintf(stderr, " 6. an NTRIP Version 1.0 Caster\n"); fprintf(stderr, " 7. an NTRIP Version 2.0 Caster in HTTP mode \n\n"); fprintf(stderr, " and forward that incoming stream (Output, Destination) to either\n\n"); fprintf(stderr, " 1. an NTRIP Version 2.0 Caster via TCP/IP (Output, Destination), or\n"); fprintf(stderr, " 2. an NTRIP Version 2.0 Caster via RTSP/RTP (Output, Destination), or\n"); fprintf(stderr, " 3. an NTRIP Version 2.0 Caster via plain UDP (Output, Destination), or\n"); fprintf(stderr, " 4. an NTRIP Version 1.0 Caster, or\n"); fprintf(stderr, " 5. an IP server via TCP/IP\n\n\n"); fprintf(stderr, "OPTIONS\n"); fprintf(stderr, " -h|? print this help screen\n\n"); fprintf(stderr, " -E Proxy server host name or address, required i.e. when\n"); fprintf(stderr, " running the program in a proxy server protected LAN,\n"); fprintf(stderr, " optional\n"); fprintf(stderr, " -F Proxy server IP port, required i.e. when running\n"); fprintf(stderr, " the program in a proxy server protected LAN, optional\n"); fprintf(stderr, " -R Reconnect mechanism with maximum delay between reconnect\n"); fprintf(stderr, " attemts in seconds, default: no reconnect activated,\n"); fprintf(stderr, " optional\n\n"); fprintf(stderr, " -M Sets the input mode (1 = Serial Port, 2 = IP server,\n"); fprintf(stderr, " 3 = File, 4 = SISNeT Data Server, 5 = UDP server, 6 = NTRIP1 Caster,\n"); fprintf(stderr, " 7 = NTRIP2 Caster in HTTP mode),\n"); fprintf(stderr, " mandatory\n\n"); fprintf(stderr, " = 1 (Serial Port):\n"); fprintf(stderr, " -i Serial input device, default: %s, mandatory if\n", ttyport); fprintf(stderr, " =1\n"); fprintf(stderr, " -b Serial input baud rate, default: 19200 bps, mandatory\n"); fprintf(stderr, " if =1\n"); fprintf(stderr, " -f Name of initialization file to be send to input device,\n"); fprintf(stderr, " optional\n\n"); fprintf(stderr, " = 2|5 (IP port | UDP port):\n"); fprintf(stderr, " -H Input host name or address, default: 127.0.0.1,\n"); fprintf(stderr, " mandatory if = 2|5\n"); fprintf(stderr, " -P Input port, default: 1025, mandatory if = 2|5\n"); fprintf(stderr, " -f Name of initialization file to be send to server,\n"); fprintf(stderr, " optional\n"); fprintf(stderr, " -x User ID to access incoming stream, optional\n"); fprintf(stderr, " -y Password, to access incoming stream, optional\n"); fprintf(stderr, " -B Bind to incoming UDP stream, optional for = 5\n\n"); fprintf(stderr, " = 3 (File):\n"); fprintf(stderr, " -s File name to simulate stream by reading data from (log)\n"); fprintf(stderr, " file, default is %s, mandatory for = 3\n\n", filepath); fprintf(stderr, " = 4 (SISNeT Data Server):\n"); fprintf(stderr, " -H SISNeT Data Server name or address,\n"); fprintf(stderr, " default: 131.176.49.142, mandatory if = 4\n"); fprintf(stderr, " -P SISNeT Data Server port, default: 7777, mandatory if\n"); fprintf(stderr, " = 4\n"); fprintf(stderr, " -u SISNeT Data Server user ID, mandatory if = 4\n"); fprintf(stderr, " -l SISNeT Data Server password, mandatory if = 4\n"); fprintf(stderr, " -V SISNeT Data Server Version number, options are 2.1, 3.0\n"); fprintf(stderr, " or 3.1, default: 3.1, mandatory if = 4\n\n"); fprintf(stderr, " = 6|7 (NTRIP Version 1.0|2.0 Caster):\n"); fprintf(stderr, " -H Source caster name or address, default: 127.0.0.1,\n"); fprintf(stderr, " mandatory if = 6|7\n"); fprintf(stderr, " -P Source caster port, default: 2101, mandatory if\n"); fprintf(stderr, " = 6|7\n"); fprintf(stderr, " -D Source caster mountpoint for stream input, mandatory if\n"); fprintf(stderr, " = 6|7\n"); fprintf(stderr, " -U Source caster user Id for input stream access, mandatory\n"); fprintf(stderr, " for protected streams if = 6|7\n"); fprintf(stderr, " -W Source caster password for input stream access, mandatory\n"); fprintf(stderr, " for protected streams if = 6|7\n\n"); fprintf(stderr, " -O Sets output mode for communication with destination caster / server\n"); fprintf(stderr, " 1 = http : NTRIP Version 2.0 Caster in TCP/IP mode\n"); fprintf(stderr, " 2 = rtsp : NTRIP Version 2.0 Caster in RTSP/RTP mode\n"); fprintf(stderr, " 3 = ntrip1: NTRIP Version 1.0 Caster\n"); fprintf(stderr, " 4 = udp : NTRIP Version 2.0 Caster in Plain UDP mode\n"); fprintf(stderr, " 5 = tcpip : IP server in TCP/IP mode\n\n\n"); fprintf(stderr, " Defaults to NTRIP1.0, but will change to 2.0 in future versions\n"); fprintf(stderr, " Note that the program automatically falls back from mode rtsp to mode http and\n"); fprintf(stderr, " further to mode ntrip1 if necessary.\n\n"); fprintf(stderr, " -a Destination caster/server name or address, default: " NTRIP_CASTER ",\n"); fprintf(stderr, " mandatory\n"); fprintf(stderr, " -p Destination caster/server port, default: 2101,\n"); fprintf(stderr, " mandatory\n"); fprintf(stderr, " -m Destination caster mountpoint for stream upload,\n"); fprintf(stderr, " only for NTRIP destination casters, mandatory\n"); fprintf(stderr, " -n Destination caster user ID for stream upload to mountpoint,\n"); fprintf(stderr, " only for NTRIP Version 2.0 destination casters, mandatory\n"); fprintf(stderr, " -c Destination caster password for stream upload to mountpoint,\n"); fprintf(stderr, " only for NTRIP destination casters, mandatory\n"); fprintf(stderr, " -N Sourcetable STR-record\n"); fprintf(stderr, " optional for NTRIP Version 2.0 in RTSP/RTP and TCP/IP mode\n\n"); exit(rc); } /* usage */ /********************************************************************/ /* signal handling */ /********************************************************************/ #ifdef __GNUC__ static void handle_sigint(int sig __attribute__((__unused__))) #else /* __GNUC__ */ static void handle_sigint(int sig) #endif /* __GNUC__ */ { sigint_received = 1; fprintf(stderr, "\nWARNING: SIGINT received - ntripserver terminates\n"); } #ifndef WINDOWSVERSION #ifdef __GNUC__ static void handle_alarm(int sig __attribute__((__unused__))) #else /* __GNUC__ */ static void handle_alarm(int sig) #endif /* __GNUC__ */ { sigalarm_received = 1; fprintf(stderr, "ERROR: more than %d seconds no activity\n", ALARMTIME); } #ifdef __GNUC__ static void handle_sigpipe(int sig __attribute__((__unused__))) #else /* __GNUC__ */ static void handle_sigpipe(int sig) #endif /* __GNUC__ */ { sigpipe_received = 1; } #endif /* WINDOWSVERSION */ static void setup_signal_handler(int sig, void (*handler)(int)) { #if _POSIX_VERSION > 198800L struct sigaction action; action.sa_handler = handler; sigemptyset(&(action.sa_mask)); sigaddset(&(action.sa_mask), sig); action.sa_flags = 0; sigaction(sig, &action, 0); #else signal(sig, handler); #endif return; } /* setupsignal_handler */ /******************************************************************** * base64-encoding * *******************************************************************/ static const char encodingTable[64] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' }; /* does not buffer overrun, but breaks directly after an error */ /* returns the number of required bytes */ static int encode(char *buf, int size, const char *user, const char *pwd) { unsigned char inbuf[3]; char *out = buf; int i, sep = 0, fill = 0, bytes = 0; while (*user || *pwd) { i = 0; while (i < 3 && *user) inbuf[i++] = *(user++); if (i < 3 && !sep) { inbuf[i++] = ':'; ++sep; } while (i < 3 && *pwd) inbuf[i++] = *(pwd++); while (i < 3) { inbuf[i++] = 0; ++fill; } if (out - buf < size - 1) *(out++) = encodingTable[(inbuf[0] & 0xFC) >> 2]; if (out - buf < size - 1) *(out++) = encodingTable[((inbuf[0] & 0x03) << 4) | ((inbuf[1] & 0xF0) >> 4)]; if (out - buf < size - 1) { if (fill == 2) *(out++) = '='; else *(out++) = encodingTable[((inbuf[1] & 0x0F) << 2) | ((inbuf[2] & 0xC0) >> 6)]; } if (out - buf < size - 1) { if (fill >= 1) *(out++) = '='; else *(out++) = encodingTable[inbuf[2] & 0x3F]; } bytes += 4; } if (out - buf < size) *out = 0; return bytes; }/* base64 Encoding */ /******************************************************************** * send message to caster * *********************************************************************/ static int send_to_caster(char *input, sockettype socket, int input_size) { int send_error = 1; if ((send(socket, input, (size_t) input_size, 0)) != input_size) { fprintf(stderr, "WARNING: could not send full header to Destination caster\n"); send_error = 0; } #ifndef NDEBUG else { fprintf(stderr, "\nDestination caster request:\n"); fprintf(stderr, "%s\n", input); } #endif return send_error; }/* send_to_caster */ /******************************************************************** * reconnect * *********************************************************************/ int reconnect(int rec_sec, int rec_sec_max) { fprintf(stderr, "reconnect in <%d> seconds\n\n", rec_sec); rec_sec *= 2; if (rec_sec > rec_sec_max) rec_sec = rec_sec_max; #ifndef WINDOWSVERSION sleep(rec_sec); sigpipe_received = 0; #else Sleep(rec_sec*1000); #endif sigalarm_received = 0; return rec_sec; } /* reconnect */ /******************************************************************** * close session * *********************************************************************/ static void close_session(const char *caster_addr, const char *mountpoint, int session, char *rtsp_ext, int fallback) { int size_send_buf; char send_buf[BUFSZ]; if (!fallback) { if ((gps_socket != INVALID_SOCKET) && ((inputmode == TCPSOCKET) || (inputmode == UDPSOCKET) || (inputmode == NTRIP1_IN) || (inputmode == NTRIP2_HTTP_IN) || (inputmode == SISNET))) { if (closesocket(gps_socket) == -1) { perror("ERROR: close input device "); exit(0); } else { gps_socket = -1; #ifndef NDEBUG fprintf(stderr, "close input device: successful\n"); #endif } } else if ((gps_serial != INVALID_HANDLE_VALUE) && (inputmode == SERIAL)) { #ifndef WINDOWSVERSION if (close(gps_serial) == INVALID_HANDLE_VALUE) { perror("ERROR: close input device "); exit(0); } #else if(!CloseHandle(gps_serial)) { fprintf(stderr, "ERROR: close input device "); exit(0); } #endif else { gps_serial = INVALID_HANDLE_VALUE; #ifndef NDEBUG fprintf(stderr, "close input device: successful\n"); #endif } } else if ((gps_file != -1) && (inputmode == INFILE)) { if (close(gps_file) == -1) { perror("ERROR: close input device "); exit(0); } else { gps_file = -1; #ifndef NDEBUG fprintf(stderr, "close input device: successful\n"); #endif } } } if (socket_udp != INVALID_SOCKET) { if (udp_cseq > 2) { size_send_buf = snprintf(send_buf, sizeof(send_buf), "TEARDOWN rtsp://%s%s/%s RTSP/1.0\r\n" "CSeq: %d\r\n" "Session: %u\r\n" "\r\n", caster_addr, rtsp_ext, mountpoint, udp_cseq++, session); if ((size_send_buf >= (int) sizeof(send_buf)) || (size_send_buf < 0)) { fprintf(stderr, "ERROR: Destination caster request to long\n"); exit(0); } send_to_caster(send_buf, socket_tcp, size_send_buf); strcpy(send_buf, ""); size_send_buf = recv(socket_tcp, send_buf, sizeof(send_buf), 0); send_buf[size_send_buf] = '\0'; #ifndef NDEBUG fprintf(stderr, "Destination caster response:\n%s", send_buf); #endif } if (closesocket(socket_udp) == -1) { perror("ERROR: close udp socket"); exit(0); } else { socket_udp = -1; #ifndef NDEBUG fprintf(stderr, "close udp socket: successful\n"); #endif } } if (socket_tcp != INVALID_SOCKET) { if (closesocket(socket_tcp) == -1) { perror("ERROR: close tcp socket"); exit(0); } else { socket_tcp = -1; #ifndef NDEBUG fprintf(stderr, "close tcp socket: successful\n"); #endif } } } /* close_session */