File: tcpecho.c
   1 /*
   2 The MIT License (MIT)
   3 
   4 Copyright © 2020-2025 pacman64
   5 
   6 Permission is hereby granted, free of charge, to any person obtaining a copy of
   7 this software and associated documentation files (the “Software”), to deal
   8 in the Software without restriction, including without limitation the rights to
   9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  10 of the Software, and to permit persons to whom the Software is furnished to do
  11 so, subject to the following conditions:
  12 
  13 The above copyright notice and this permission notice shall be included in all
  14 copies or substantial portions of the Software.
  15 
  16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22 SOFTWARE.
  23 */
  24 
  25 /*
  26 You can build this command-line app by running
  27 
  28 cc -Wall -s -O2 -o ./tcpecho ./tcpecho.c
  29 */
  30 
  31 #include <arpa/inet.h>
  32 #include <fcntl.h>
  33 #include <netinet/in.h>
  34 #include <stdbool.h>
  35 #include <stdio.h>
  36 #include <stdlib.h>
  37 #include <string.h>
  38 #include <sys/socket.h>
  39 #include <sys/types.h>
  40 #include <unistd.h>
  41 
  42 #ifdef _WIN32
  43 #include <windows.h>
  44 #endif
  45 
  46 const char* info = ""
  47 "tcpecho [port] [words...]\n"
  48 "\n"
  49 "\n"
  50 "Start a TCP server which always replies with a plain-text line made from\n"
  51 "the arguments given after the port number, joined by single spaces.\n"
  52 "\n"
  53 "\n"
  54 "Options, all of which can start with either 1 or 2 dashes:\n"
  55 "\n"
  56 "\n"
  57 "  -h          show this help message\n"
  58 "  -help       show this help message\n"
  59 "";
  60 
  61 // is_help_option simplifies control-flow for func main
  62 bool is_help_option(const char* s) {
  63     return (s[0] == '-') && (
  64         strcmp(s, "-h") == 0 ||
  65         strcmp(s, "-help") == 0 ||
  66         strcmp(s, "--h") == 0 ||
  67         strcmp(s, "--help") == 0
  68     );
  69 }
  70 
  71 int main(int argc, char** argv) {
  72 #ifdef _WIN32
  73     setmode(fileno(stdin), O_BINARY);
  74     // ensure output lines end in LF instead of CRLF on windows
  75     setmode(fileno(stdout), O_BINARY);
  76     setmode(fileno(stderr), O_BINARY);
  77 #endif
  78 
  79     // handle any of the help options, if given
  80     if (argc > 1 && is_help_option(argv[1])) {
  81         puts(info);
  82         return 0;
  83     }
  84 
  85     if (argc < 2) {
  86         const char* m = "you forgot the port number as the leading argument";
  87         fprintf(stderr, "\x1b[31m%s\x1b[0m\n", m);
  88         return 1;
  89     }
  90 
  91     char* end;
  92     int port = strtol(argv[1], &end, 10);
  93     if (*end != 0 || port < 1 || port > 65535) {
  94         const char* msg;
  95         msg = "isn't a valid port number";
  96         fprintf(stderr, "\x1b[31m%s %s\x1b[0m\n", argv[1], msg);
  97         msg = "maybe you forgot the port number as the leading argument";
  98         fprintf(stderr, "\x1b[31m%s\x1b[0m\n", msg);
  99         return 1;
 100     }
 101 
 102     // count spaces between words
 103     size_t msglen = argc - 2;
 104     // count word lengths
 105     for (size_t i = 2; i < argc; i++) {
 106         msglen += strlen(argv[i]);
 107     }
 108     // remember the final line-feed
 109     msglen++;
 110 
 111     // also remember that strings are ended with an extra null byte
 112     char* msg = malloc(msglen + 1);
 113     if (msg == NULL) {
 114         const char* m = "can't get enough memory for the response message";
 115         fprintf(stderr, "\x1b[31m%s\x1b[0m\n", m);
 116         return 1;
 117     }
 118 
 119     for (size_t i = 2, pos = 0; i < argc; i++) {
 120         if (i > 2) {
 121             msg[pos] = ' ';
 122             pos++;
 123         }
 124 
 125         for (size_t j = 0; argv[i][j] != 0; j++) {
 126             msg[pos] = argv[i][j];
 127             pos++;
 128         }
 129     }
 130 
 131     msg[msglen - 1] = '\n';
 132     msg[msglen] = 0;
 133 
 134     int fd = socket(AF_INET, SOCK_STREAM, 0);
 135     if (fd < 0 || fcntl(fd, F_GETFD) == -1) {
 136         free(msg);
 137         fprintf(stderr, "\x1b[31mcan't open socket connection\x1b[0m\n");
 138         return 1;
 139     }
 140 
 141     struct sockaddr_in srv;
 142     memset(&srv, 0, sizeof(srv));
 143     srv.sin_family = AF_INET;
 144     srv.sin_addr.s_addr = htonl(INADDR_ANY);
 145     srv.sin_port = htons(port);
 146 
 147     if (bind(fd, (struct sockaddr*)&srv, sizeof(srv)) != 0) {
 148         free(msg);
 149         fprintf(stderr, "\x1b[31mcan't open socket connection\x1b[0m\n");
 150         return 1;
 151     }
 152 
 153     if (listen(fd, 1) != 0) {
 154         free(msg);
 155         fprintf(stderr, "\x1b[31mcan't listen on open connection\x1b[0m\n");
 156         return 1;
 157     }
 158 
 159     while (true) {
 160         int conn = accept(fd, NULL, NULL);
 161         send(conn, msg, msglen, 0);
 162         close(conn);
 163     }
 164 
 165     free(msg);
 166     return 0;
 167 }