gawk

Sed-like interface to the Gopher protocol
git clone git://jacobedwards.org/gawk
Log | Files | Refs | LICENSE

net.c (3606B)


      1 /* Copyright 2021 Jacob R. Edwards
      2  *
      3  * This file is part of gawk.
      4  *
      5  * This program is free software: you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation, either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * This program is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include <sys/socket.h>
     20 #include <errno.h>
     21 #include <netdb.h>
     22 #include <poll.h>
     23 #include <stdio.h>
     24 #include <string.h>
     25 #include <unistd.h>
     26 
     27 #include "gawk.h"
     28 #include "util.h"
     29 
     30 int
     31 resolve(char const *host, char const *port)
     32 {
     33 	int error, s;
     34 	struct addrinfo hints;
     35 	struct addrinfo *ai, *ai0;
     36 
     37 	memset(&hints, 0, sizeof(hints));
     38 	hints.ai_family = AF_UNSPEC;
     39 	hints.ai_socktype = SOCK_STREAM;
     40 	hints.ai_protocol = IPPROTO_TCP;
     41 
     42 	error = getaddrinfo(host, port, &hints, &ai0);
     43 	if (error) {
     44 		warn(0, "unable to resolve '%s' (%s): %s",
     45 		    host, port, gai_strerror(error));
     46 		return -1;
     47 	}
     48 
     49 	s = -1;
     50 	for (ai = ai0; ai && s == -1; ai = ai->ai_next) {
     51 		s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
     52 		if (s != -1 && connect(s, ai->ai_addr, ai->ai_addrlen) == -1) {
     53 			close(s);
     54 			s = -1;
     55 		}
     56 	}
     57 	freeaddrinfo(ai0);
     58 
     59 	if (s == -1)
     60 		warn(errno, "unable to connect '%s' (%s)", host, port);
     61 	return s;
     62 }
     63 
     64 int
     65 gphre(int sock, char const *message)
     66 {
     67 	char tmp[MY_PATH_MAX];
     68 	int n;
     69 
     70 	n = strcspn(message, "?");
     71 	if (message[n] == '\0')
     72 		n = send(sock, message, n, 0);
     73 	else {
     74 		strlcpy(tmp, message, n + 1);
     75 		tr(tmp + n, message + n, '?', ' ');
     76 		tmp[n] = '\t';
     77 		n = send(sock, tmp, strlen(tmp), 0);
     78 	}
     79 
     80 	if (n == -1) {
     81 		warn(errno, "unable to send message");
     82 		return 1;
     83 	}
     84 	if (shutdown(sock, SHUT_WR)) {
     85 		warn(errno, "shutdown");
     86 		return 1;
     87 	}
     88 	return 0;
     89 }
     90 
     91 int
     92 recvtxt(int s, FILE *output)
     93 {
     94 	FILE *input;
     95 	char *bufp;
     96         char buf[MY_LINE_MAX];
     97 
     98 	input = fdopen(s, "r");
     99 	if (input == NULL) {
    100 		warn(errno, "fdopen");
    101 		return 1;
    102 	}
    103 
    104 	/*
    105 	 * While the specification says strip leading periods if
    106          * there are multiple I think it must mean only one (see
    107 	 * RFC 1436 Appendix).
    108 	 */
    109 	while ((bufp = fgets(buf, sizeof(buf), input)) != NULL) {
    110 		if (*bufp == '.')
    111 			if (strcmp(++bufp, "\r\n") == 0)
    112 				break;
    113 		if (fputs(bufp, output) == EOF)
    114 			return 1;
    115 	}
    116 
    117 	return 0;
    118 }
    119 
    120 int
    121 recvbin(int s, FILE *output)
    122 {
    123 	char buf[MY_CHUNK_MAX];
    124 	ssize_t bytes;
    125 
    126 	while ((bytes = recv(s, buf, sizeof(buf), 0)) > 0)
    127 		/* NOTE: cast is safe, bytes _is_ >0 */
    128 		if (fwrite(buf, 1, bytes, output) != (size_t)bytes)
    129 			return 1;
    130 	return 0;
    131 }
    132 
    133 int
    134 gphto(int sock, char const *path, int bin, int timeout)
    135 {
    136 	FILE *fp;
    137 	int status;
    138 	struct pollfd pfd;
    139 
    140 	pfd.fd = sock;
    141 	pfd.events = POLLRDNORM;
    142 	if (poll(&pfd, 1, timeout) < 1) {
    143 		warn(errno, "unable to recieve response");
    144 		return 1;
    145 	}
    146 
    147 	fp = wfopen(path, "w+");
    148 	if (fp == NULL)
    149 		return 1;
    150 
    151 	if (bin)
    152 		status = recvbin(sock, fp);
    153 	else	status = recvtxt(sock, fp);
    154 	if (wfclose(fp) || status)
    155 		return 1;
    156 	return 0;
    157 }
    158 
    159 int
    160 gopher(char **addr, char const *path, int bin, int timeout)
    161 {
    162 	int sock;
    163 
    164 	sock = resolve(addr[AR_HOST], addr[AR_PORT]);
    165 	if (sock == -1)
    166 		return 1;
    167 
    168 	if (gphre(sock, addr[AR_PATH]) != 0 ||
    169 	    gphto(sock, path, bin, timeout) != 0) {
    170 		close(sock);
    171 		return 1;
    172 	}
    173 	return close(sock);
    174 }