/* GEMSERV: Generic Multi Server Version 1.0 * (c) DeMO, 2006 * --------------------------------------------------------- * - Allows to listen to several ports TCP and UDP simultaneously * - All data received is sent to stdout (it can be piped to other processes) * - stdin data is sent to all connected clients * * ------------------------------------------------------------------------- * TODO: * - Allow to send configuration commands to server from stdin * Prepend stdin input with special character (! for example) * * - Setup a connection matrix for communication sockets * Data received by each socket is sent to descriptor in that matrix * This allows to build simple instant messanging solutions * * - Allow client functionality * stdin is directed to a set of connected sockets (multiclient) * * - Configuration file * To read program configuration * * - Allow external processes to attach to ports * Input and Output sockets are connected to stdin and stdout in child process * */ /* General Includes */ #include #include #include /* Unix Specific */ #include #include /* Network Specific */ #include #include #include #include #include #include #include /* Programa Constants & Macros -----------------------------*/ #define T_UDP SOCK_DGRAM #define T_TCP SOCK_STREAM #define INOUT_CLIENT 0 #define BUFSIZE 2048 /* FIXME: Static Array for UDP connections */ #define NPORTS 100 #ifdef DEBUG #define PDEBUG(fmt, arg...) \ fprintf(stderr, "%ld [GEMSERV]: " fmt, time(NULL), ##arg) #else #define PDEBUG(fmt, arg...) do { } while(0) #endif /* Global Vars --------------------------------------------*/ /* Server can listen to several ports */ static int *sport_type = NULL, n_port_types = 0; static int *s_accept = NULL, n_ports = 0; /* Real communication sockets */ static int *s_comm = NULL, n_comm = 0; static struct sockaddr_in uclient[NPORTS]; /* Only 1 UDP client by accept port*/ /* Helper Functions ***************************/ /* Find empty connection entry*/ static int find_empty_entry (int *slist, int n) { int i = 0; while (i < n && slist[i] != -1) i++; return i; } static int add_handler (int **slist, int *n) { int i; if ((i = find_empty_entry (*slist, *n)) == *n) { (*n)++; *slist = realloc (*slist, (*n) * sizeof(int)); } return i; } int create_initial_socket (char *ip, char *port, int type, int inout) { int j, i, ops = 1; int **list, *n; struct sockaddr_in server; int sa_len = sizeof(struct sockaddr_in); list = (inout) ? &s_accept : &s_comm; n = (inout) ? &n_ports : &n_comm; server.sin_addr.s_addr = (inout) ? INADDR_ANY : inet_addr(ip); server.sin_family = AF_INET; server.sin_port = htons(atoi(port)); /* Initialise socket. */ /* XXX: No error check for this example */ (*list)[(j = add_handler (list, n))] = socket (PF_INET, type, 0); if (inout) { sport_type[(i = add_handler (&sport_type, &n_port_types))] = type; /* Set reuse address/port socket option */ setsockopt ((*list)[j], SOL_SOCKET, SO_REUSEADDR, &ops, sizeof(ops)); bind ((*list)[j], (struct sockaddr *) &server, sa_len); } if (type == T_TCP) i = (inout) ? listen ((*list)[j], 10) : connect ((*list)[j], (struct sockaddr*)&server, sa_len); return 0; } /* Main application */ int main (int argc, char *argv[]) { fd_set rfds; struct timeval tv; int i, max, n_res, len, ilen, j, k; int loop4ever = 1; char buffer[BUFSIZE], ibuffer[BUFSIZE]; struct sockaddr_in client; socklen_t sa_len = sizeof(struct sockaddr_in); int arg_flag, type; char *aux, *aux1; int hub = 0; if (argc == 1) { printf ("Usage: gemserv [-client ((T|U):ip:port)+] [-server ((T|U):port)+]\n\n"); exit (1); } /* TODO: Parse parameters */ arg_flag = 0; for (i = 1; i < argc; i++) { if (strncmp (argv[i], "-hub", 4) == 0) { hub = 1; continue; } if (strncmp (argv[i], "-client", 7) == 0) { PDEBUG ("** Reading Client information\n"); arg_flag = 1; /* Process client data */ continue; } if (strncmp (argv[i], "-server", 7) == 0) { PDEBUG ("** Reading Server information\n"); arg_flag = 2; /* Process client data */ continue; } aux = argv[i]; type = (*aux == 'T') ? T_TCP : T_UDP; aux += 2; switch (arg_flag) { case 1: *(aux1 = strchr (aux, ':')) = 0; PDEBUG ("Connecting to '%s':'%s' (%d)\n", aux, aux1 + 1, type); create_initial_socket (aux, aux1 + 1, type, INOUT_CLIENT); break; case 2: PDEBUG ("Accepting '%s' (%d)\n", aux, type); create_initial_socket (NULL, aux, type, INOUT_CLIENT + 1); default: break; } } /* Main loop */ while (loop4ever) { /* Set File Descriptor Set */ FD_ZERO(&rfds); max = 0; PDEBUG ("Building select data %d, %d\n", n_ports, n_comm); for (i = 0; i < n_ports; i++) { FD_SET(s_accept[i], &rfds); if (s_accept[i] >= max) max = s_accept[i] + 1; } for (i = 0; (i < n_comm) && s_comm[i] != -1; i++) { FD_SET(s_comm[i], &rfds); if (s_comm[i] >= max) max = s_comm[i] + 1; } /* Add stdin to rdset */ FD_SET(0, &rfds); /* 4 sec Timeout*/ tv.tv_sec = 4; tv.tv_usec = 0; if ((n_res = select (max, &rfds, NULL, NULL, &tv)) < 0) perror ("select:"); else { /* Check stdin data */ ilen = 0; if (FD_ISSET(0, &rfds)) { PDEBUG ("stdin data available... read %d bytes\n", ilen); /* Get stdin data to send to all clients */ ilen = read (0, ibuffer, BUFSIZE); if (ilen == 0) { /* XXX: Piped mode we got file descriptor active but no data is available*/ loop4ever = 0; write (1, "DONE!!!\n", 8); continue; } PDEBUG ("stdin data available... read %d bytes\n", ilen); ibuffer[ilen] = 0; } if (n_res) { PDEBUG ("---> Got Something (%d connections active): %d\n", n_comm, n_res); for (i = 0; i < n_comm; i++) { if (s_comm[i] == -1) continue; if (FD_ISSET(s_comm[i], &rfds)) { if ((len = recv (s_comm[i], buffer, BUFSIZE, 0)) <= 0) { PDEBUG ("0 bytes read.... removing socket\n"); s_comm[i] = -1; continue; } PDEBUG ("Read %d bytes from TCP stream\n", len); buffer[len] = 0; write (1, buffer, len); /* Send to each connected socket*/ if (hub) for (k = 0; k < n_comm; k++) if (i != k) send (s_comm[k], buffer, len, 0); } if (ilen) send (s_comm[i], ibuffer, ilen, 0); } /* Check accept sockets */ for (i = 0; i < n_ports;i++) { if (FD_ISSET(s_accept[i], &rfds)) { if (sport_type[i] == T_TCP) { s_comm[(j = add_handler (&s_comm, &n_comm))] = accept (s_accept[i], (struct sockaddr*) &client, &sa_len); PDEBUG ("Accepting connection for %d channel\n", j); } else /* Process UDP data */ { PDEBUG ("Processing %d UDP port\n", s_accept[i]); len = recvfrom (s_accept[i], buffer, BUFSIZE, 0, (struct sockaddr*) &uclient[i], &sa_len); buffer[len] = 0; write (1, buffer, len); fflush(NULL); } } if (ilen && sport_type[i] == T_UDP) sendto (s_accept[i], ibuffer, ilen, 0, (struct sockaddr*) &uclient[i], sa_len); } } else { /* Add here your idle operation */ PDEBUG ("TIMEOUT!!!!\n"); } } } return 0; }