// uses socket demo code from https://beej.us/guide/bgnet/html/single/bgnet.html // and getstdin() uses getLine() from https://stackoverflow.com/questions/4023895/ #include #include #include #include #include #include #include #include #include #include #include #define MAXDATASIZE 513 // max number of bytes we can get at once (RFC2812 says 512, plus one for null terminator) #define STDIN 0 // stdin is fd 0 #define BACKLOG 10 // maximum length to which the queue of pending connections for sockfd may grow #define BOUNCERLISTENPORT "1234" // TODO: change this to a config option! // getstdin() return codes #define OK 0 #define NO_INPUT 1 #define TOO_LONG 2 // Get stdin line with buffer overrun protection static int getstdin(char *prompt, char *buff, size_t sz) { int ch, extra; // Print optional prompt if (prompt != NULL) { printf ("%s", prompt); fflush (stdout); } // Get the intput from stdin if (fgets (buff, sz, stdin) == NULL) { return NO_INPUT; } // If it was too long, there'll be no newline. In that case, we flush // to end of line so that excess doesn't affect the next call. if (buff[strlen(buff) - 1] != '\n') { // strlen of the actually entered line, not the original array size extra = 0; while (((ch = getchar()) != '\n') && (ch != EOF)) { extra = 1; } return (extra == 1) ? TOO_LONG : OK; } // Otherwise remove newline and give string back to caller. buff[strlen(buff) - 1] = '\0'; return OK; } // Append CR-LF to the end of a string void appendcrlf(char *string) { // Make sure it doesn't already end with CR or LF while (string[strlen(string) - 1] == '\r' || string[strlen(string) - 1] == '\r') { string[strlen(string) - 1] = '\0'; } int startlen = strlen(string); string[startlen] = '\r'; string[startlen + 1] = '\n'; string[startlen + 2] = '\0'; } // get sockaddr, IPv4 or IPv6: void *get_in_addr(struct sockaddr *sa) { if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); } return &(((struct sockaddr_in6*)sa)->sin6_addr); } int createserversocket(char *host, char *port) { int sockfd; struct addrinfo hints, *servinfo, *p; int rv;// return value for getaddrinfo (for error message) char s[INET6_ADDRSTRLEN]; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if ((rv = getaddrinfo(host, port, &hints, &servinfo)) != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); return 1; } // loop through all the results and connect to the first we can for (p = servinfo; p != NULL; p = p->ai_next) { if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) { perror("bouncer-server: socket"); continue; } if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { close(sockfd); perror("bouncer-server: connect"); continue; } break; } if (p == NULL) { fprintf(stderr, "bouncer-server: failed to connect\n"); return 2; } inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr), s, sizeof s); printf("bouncer-server: connecting to %s\n", s); freeaddrinfo(servinfo); // all done with this structure return sockfd; } int createclientsocket(char *listenport) { listenport = BOUNCERLISTENPORT; int listener; // listening socket descriptor int rv; // return value for getaddrinfo (for error message) struct addrinfo hints, *ai, *p; // get us a socket and bind it memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_PASSIVE; if ((rv = getaddrinfo(NULL, listenport, &hints, &ai)) != 0) { fprintf(stderr, "selectserver: %s\n", gai_strerror(rv)); exit(1); } // Try for IPv6 for (p = ai; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET6) { listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (listener != -1) { // success, got IPv6! printf("success, got IPv6! ai_family: %d\n", p->ai_family); break; } } } // Try for IPv4 if IPv6 failed if (listener < 0) { for (p = ai; p != NULL; p = p->ai_next) { if (p->ai_family == AF_INET) { listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol); if (listener != -1) { // moderate success, got IPv4! printf("moderate success, got IPv4! ai_family: %d\n", p->ai_family); break; } } } } if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) { // failed to bind close(listener); printf("bouncer-client: failed to bind, exiting...\n"); exit(1); } // if we got here, it means we didn't get bound if (p == NULL) { fprintf(stderr, "selectserver: failed to bind\n"); exit(2); } freeaddrinfo(ai); // all done with this // listen if (listen(listener, BACKLOG) == -1) { perror("listen"); exit(1); } return listener; } void dochat(int *serversockfd, int *clientsockfd) { char serverbuf[MAXDATASIZE]; int numbytes; // Number of bytes received from remote server char outgoingmsg[MAXDATASIZE]; // String to send to server int outgoingmsgrc; // Return code from getstdin() for outgoing message fd_set rfds; while (1) { FD_ZERO(&rfds); // clear entries from fd set FD_SET(STDIN, &rfds); // add STDIN (fd 0) to read fds to monitor FD_SET(*serversockfd, &rfds); // add our network socket to monitor // TODO - Change this to use number of fds + 1 instead of sockfd + 1 (I don't think they will be the same with multiple sockets/connections) if (select(*serversockfd + 1, &rfds, NULL, NULL, NULL) < 0) { // network socket + 1, rfds, no writes, no exceptions/errors, no timeout printf("receive error, exiting!?\n"); perror("select"); // TODO exit here? let's try it. exit(1); } if (FD_ISSET(*serversockfd, &rfds)) { printf("reading socket!\n"); if ((numbytes = recv(*serversockfd, serverbuf, MAXDATASIZE - 1, 0)) == -1) { printf("receive error (-1), exiting...\n"); perror("recv"); exit(1); } else if (numbytes == 0) { printf("socket closed (or no data received) (0), exiting...\n"); perror("recv"); exit(1); } serverbuf[numbytes] = '\0'; printf("RECEIVED: %s\n", serverbuf); } if (FD_ISSET(STDIN, &rfds)) { printf("reading stdin!\n"); outgoingmsgrc = getstdin(NULL, outgoingmsg, sizeof(outgoingmsg)); if (outgoingmsgrc == NO_INPUT) { printf("\nError! No input.\n"); } else if (outgoingmsgrc == TOO_LONG) { printf ("Error! Too long. Would have allowed up to: [%s]\n", outgoingmsg); } appendcrlf(outgoingmsg); if (send(*serversockfd, outgoingmsg, strlen(outgoingmsg), 0) == -1) { printf("send error, exiting...\n"); perror("send"); } } } } int main(int argc, char *argv[]) { if (argc != 3) { fprintf(stderr,"usage: %s [hostname] [port]\n", argv[0]); exit(1); } // TODO: see if any of this can be shared (i.e. 1. avoid code duplication, and 2. see if variables can be shared between client/server sockets) // TODO: track fdmax // I will try to keep to the notation of "server" meaning the real IRCd, "bouncer" meaning the bouncer, and "client" meaning the real IRC client // BOUNCER-TO-SERVER socket things int serversockfd = createserversocket(argv[1], argv[2]); int clientsockfd = createclientsocket(BOUNCERLISTENPORT); dochat(&serversockfd, &clientsockfd); printf("dochat() complete, closing socket...\n"); close(serversockfd); return 0; }