1 /*
2  *	(c) 1998-01 Jirka Hanika <geo@cuni.cz>
3  *
4  *	This single source file src/client.cc, but NOT THE REST OF THIS PACKAGE,
5  *	is considered to be in Public Domain. Parts of this single source file may be
6  *	freely incorporated into any commercial or other software.
7  *
8  *	Most files in this package are strictly covered by the General Public
9  *	License version 2, to be found in doc/COPYING. Should GPL and the paragraph
10  *	above come into any sort of legal conflict, GPL takes precendence.
11  *
12  *	This file implements support routines for a simple TTSCP client.
13  *	See doc/english/ttscp.doc for a preliminary technical specification.
14  *
15  *	This file can be included with cfg pointing to two very different
16  *	structures.  The usual interpretation, the one compiled into client.o,
17  *	is a few hundred bytes long structure.  However, when the "say-epos" client
18  *	is compiled, this file is #included directly and now cfg points to
19  *	a fake constant structure with only a few items needed to compile
20  *	this file.  This scheme is probably too clever to keep, but anyway,
21  *	at the moment it prevents using client.o for actual client stuff.
22  */
23 
24 #ifdef THIS_IS_A_TTSCP_CLIENT
25 
26 	#define INITIAL_SCRATCH_SPACE 16384
D_PRINT(int,...)27 	void D_PRINT(int, ...) {};
28 	#define xmalloc malloc
29 	#define xrealloc realloc
30 
31 	struct pseudo_static_configuration
32 	{
33 		int asyncing;
34 		int scratch_size;
35 		int paranoid;
36 		int listen_port;
37 	};
38 
39 	pseudo_static_configuration pseudocfg = {1, INITIAL_SCRATCH_SPACE, 0, TTSCP_PORT};
40 
41 	pseudo_static_configuration *scfg = &pseudocfg;
42 
43 	char *scratch = (char *)malloc(INITIAL_SCRATCH_SPACE + 2);
44 #else
45 	#include "epos.h"
46 #endif
47 
48 
49 #define PUBLIC_TTSCP_SERVER   "epos.ure.cas.cz"
50 
51 #include "client.h"
52 
53 #ifdef HAVE_UNISTD_H
54 	#include <unistd.h>
55 #endif
56 
57 #ifdef HAVE_UNIX_H
58 	#include <unix.h>
59 #endif
60 
61 #ifdef HAVE_SYS_SOCKET_H
62 	#include <sys/socket.h>
63 #endif
64 
65 #ifdef HAVE_NETINET_IN_H
66 	#include <netinet/in.h>
67 #endif
68 
69 #ifdef HAVE_NETDB_H
70 	#include <netdb.h>
71 #endif
72 
73 #ifdef HAVE_IO_H
74 	#include <io.h>
75 #endif
76 
77 #ifdef HAVE_WINSOCK2_H
78 	#include <winsock2.h>
79 	#define HAVE_WINSOCK
80 #else
81 	#ifdef HAVE_WINSOCK_H
82 		#include <winsock.h>
83 		#define HAVE_WINSOCK
84 	#endif
85 #endif
86 
87 #ifdef HAVE_SYS_TYPES_H
88 	#include <sys/types.h>
89 #endif
90 
91 #ifdef HAVE_SIGNAL_H
92 	#include <signal.h>
93 #endif
94 
95 
96 /*
97  *	nonblocking sgets() - returns immediately.
98  *	tries to get a line into buffer; if it can't,
99  *	returns zero and partbuff will contain some (undefined)
100  *	data, which should be passed to the next call to
101  *	sgets() with this, but not another socket.
102  *	The "space" argument limits both buffers.
103  *
104  *	Upon the first call with this socket, *partbuff must == 0.
105  *
106  *	returns:      0   partial line in partbuff or nothing to do
107  *		positive  full line in buffer
108  *		negative  error reading socket
109  *
110  *	Our policy is not to read the socket when we've got
111  *	a partial line acquired in an earlier invocation.
112  *	This is to avoid starvation by an over-active session.
113  *	Such a session would however cause a lot of shifting
114  *	strings back and forth between the buffers.
115  *
116  *	The nonblocking sgets() works with both nonblocking and
117  *	blocking sockets (sd's).  With blocking sockets it does
118  *	block, but still may return 0 after a partial read.
119  */
120 
sgets(char * buffer,int space,int sd,char * partbuff)121 int sgets(char *buffer, int space, int sd, char *partbuff)
122 {
123 	int i, l;
124 	int result = 0;
125 
126 	if (*partbuff) {
127 		D_PRINT(1, "sgets: Appending.\n");
128 		l = strlen(partbuff);
129 		if (l > space) shriek(862, "sgets() holdback overflow"); // was: shriek(664)
130 		if (l == space) goto too_long;
131 		strcpy(buffer, partbuff);
132 		if (strchr(buffer, '\n')) goto already_enough_text;
133 	} else l = 0;
134 	result = yread(sd, buffer + l, space - l);
135 	if (result >= 0) buffer[l+result] = 0; else buffer[l] = 0;
136 	if (result <= 0) {
137 		if (result == -1 && errno == EAGAIN) {
138 			D_PRINT(2, "sgets: Nothing to do on %d\n", sd);
139 			*buffer = 0;
140 			return 0;
141 		}
142 		*partbuff = 0;	/* forgetting partial line upon EOF/error. Bad? */
143 		*buffer = 0;
144 		D_PRINT(2, "sgets: Error on socket %d\n", sd);
145 		return -1;
146 	}
147 	l += result;
148 
149 already_enough_text:
150 	for (i=0; i<l; i++) {
151 		if (buffer[i] == '\n' || !buffer[i]) {
152 			if (i && buffer[i-1] == '\r') buffer[i-1] = 0;
153 			buffer[i] = 0;
154 			if (++i < l) strcpy(partbuff, buffer+i);
155 			else *partbuff = 0;
156 			return 1;
157 		}
158 	}
159 	if (i >= space) goto too_long;
160 	buffer[i] = 0;
161 	strcpy(partbuff, buffer);
162 	D_PRINT(1, "sgets: Partial line read: %s\n", partbuff);
163 	*buffer = 0;
164 	return 0;
165 
166 too_long:
167 	strcpy(partbuff, "too long: ...");
168 	D_PRINT(3, "sgets: Too long line ignored\n");
169 //	sputs("413 Too long\n", sd);
170 	shriek(413, "Too long");
171 	*buffer = 0;
172 	return 0;
173 }
174 
175 /*
176  *	blocking sgets()
177  *	returns 0 on error (EOF), 1 on success (line read)
178  *
179  *	This code should never be called by the server code.
180  *	The socket (sd) should be blocking; otherwise this
181  *	function will busy loop over read().
182  */
183 
184 
185 char **partbuffs = (char **)xmalloc(1);
186 int *partbuff_sizes = (int *)xmalloc(1);
187 int n_partbuffs = 0;
188 
sgets(char * buffer,int buffer_size,int sd)189 int sgets(char *buffer, int buffer_size, int sd)
190 {
191 	if (sd >= n_partbuffs) {
192 		partbuffs = (char **)xrealloc(partbuffs, (sd + 1) * sizeof(char *));
193 		partbuff_sizes = (int *)xrealloc(partbuff_sizes, (sd + 1) * sizeof(char *));
194 	}
195 	while (sd >= n_partbuffs) {
196 		partbuffs[n_partbuffs] = NULL;
197 		partbuff_sizes[n_partbuffs] = NULL;
198 		n_partbuffs++;
199 	}
200 	if (!partbuffs[sd]) {
201 		partbuff_sizes[sd] = buffer_size;
202 		partbuffs[sd] = (char *)xmalloc(buffer_size);
203 		partbuffs[sd][0] = 0;
204 	}
205 	if (partbuff_sizes[sd] < buffer_size) {
206 		partbuffs[sd] = (char *)xrealloc(partbuffs[sd], buffer_size);
207 		partbuff_sizes[sd] = buffer_size;
208 	}
209 	int result = 0;
210 	while (!result) {
211 		result = sgets(buffer, buffer_size, sd, partbuffs[sd]);
212 	}
213 	return result > 0;
214 }
215 
shutdown_partbuffs()216 void shutdown_partbuffs()
217 {
218 	for (int i = 0; i < n_partbuffs; i++)
219 		free(partbuffs[i]);
220 	free(partbuffs);
221 	free(partbuff_sizes);
222 }
223 
224 
225 
226 int (*sputs_replacement)(int sd, const char *, int) = NULL;
227 
sputs(const char * buffer,int sd)228 int sputs(const char *buffer, int sd)
229 {
230 
231 	int total;
232 	int len = total = strlen(buffer);
233 	int result;
234 
235 	if (!buffer) return 0;
236 	if (sputs_replacement)
237 		return sputs_replacement(sd, buffer, len);
238 	else do {
239 		result = ywrite(sd, buffer, len);
240 		if (result == -1 && errno == EPIPE) return -1;
241 //		if (result == -1 && errno == EAGAIN && ctrl_enque)
242 //			ctrl_enque(sd, buffer, len);
243 		if (result == -1) result = 0;
244 		buffer += result;
245 		len -= result;
246 	} while (len);
247 	return total;
248 }
249 
getaddrbyname(const char * inet_name)250 int getaddrbyname(const char *inet_name)
251 {
252 #ifdef WANT_DMALLOC
253 	return htonl(INADDR_LOOPBACK);
254 #endif
255 	hostent *he = gethostbyname(inet_name);
256 	if (!he || he->h_addrtype != AF_INET || !he->h_addr_list[0])
257 		return -1;
258 	return ((in_addr *)he->h_addr_list[0])->s_addr;
259 }
260 
just_connect_socket(unsigned int ipaddr,int port)261 int just_connect_socket(unsigned int ipaddr, int port)
262 {
263 	sockaddr_in addr;
264 	int sd;
265 
266 	if (!port) {
267 		sd = just_connect_socket(ipaddr, TTSCP_PORT);
268 		if (sd == -1) sd = just_connect_socket(ipaddr, TTSCP_PORT + 1);
269 		if (sd != -1) return sd;
270 		int public_addr = getaddrbyname(PUBLIC_TTSCP_SERVER);
271 		if (public_addr == -1) return -1;
272 		if (sd == -1) sd = just_connect_socket(public_addr, TTSCP_PORT + 1);
273 		if (sd == -1) sd = just_connect_socket(public_addr, TTSCP_PORT);
274 		return sd;
275 	}
276 
277 	sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
278 	if (sd == -1) shriek(464, "No socket\n");
279 	memset(&addr, 0, sizeof(addr));
280 	addr.sin_family = AF_INET;
281 	addr.sin_port = htons(port);
282 	if (!ipaddr) {
283 //		gethostname(scratch, scfg->scratch_size);	// can be used instead of localhost
284 		strcpy(scratch, "localhost");
285 		ipaddr = getaddrbyname(scratch);
286 		if (ipaddr == -1) return -1;
287 	}
288 	addr.sin_addr.s_addr = ipaddr;
289 
290 	return connect(sd, (sockaddr *)&addr, sizeof(addr)) ? (close(sd) ,-1) : sd;
291 }
292 
connect_socket(unsigned int ipaddr,int port)293 int connect_socket(unsigned int ipaddr, int port)
294 {
295 	int sd = just_connect_socket(ipaddr, port);
296 	if (sd == -1) {
297 		shriek(473, "Server unreachable (Epos not running?)\n");
298 	}
299 	if (!sgets(scratch, scfg->scratch_size, sd)) shriek(474, "Remote server listens but discards\n");
300 	if (strncmp(scratch, "TTSCP spoken here", 18)) {
301 		scratch[15] = 0;
302 		shriek(474, "Protocol not recognized");
303 	}
304 	return sd;
305 }
306 
running_at_localhost()307 bool running_at_localhost()
308 {
309 	int j = just_connect_socket(0, scfg->listen_port);
310 	if (j == -1) return false;
311 	close(j);
312 	return true;
313 }
314 
get_handle(int sd)315 char *get_handle(int sd)
316 {
317 	do {
318 		sgets(scratch, scfg->scratch_size, sd);
319 	} while (*scratch && strncmp(scratch, "handle: ", 8));
320 	if (!*scratch) {
321 		printf("NULL handle\n");
322 		return NULL;
323 	}
324 	return strdup(scratch + 8);
325 }
326 
xmit_option(const char * name,const char * value,int sd)327 void xmit_option(const char *name, const char *value, int sd)
328 {
329 	sputs("setl ", sd);
330 	sputs(name, sd);
331 	sputs(" ", sd);
332 	sputs(value, sd);
333 	sputs("\r\n", sd);
334 }
335 
336 #define ERROR_CODE ((scratch[0]-'0')*100+(scratch[1]-'0')*10+(scratch[2]-'0'))
337 
sync_finish_command(int ctrld)338 int sync_finish_command(int ctrld)
339 {
340 	while (sgets(scratch, scfg->scratch_size, ctrld)) {
341 		scratch[scfg->scratch_size] = 0;
342 //		printf("Received: %s\n", scratch);
343 		switch(*scratch) {
344 			case '1': continue;
345 			case '2': return 0;
346 			case '3': break;
347 			case '4': // printf("%s\n", scratch+strspn(scratch, "0123456789x "));
348 				  return ERROR_CODE;
349 			case '6': if (!strncmp(scratch, "600 ", 4)) {
350 					  return 0;
351 				  } /* else fall through */
352 			case '8': // printf("%s\n", scratch+strspn(scratch, "0123456789x "));
353 				  return ERROR_CODE;
354 
355 			case '5':
356 			case '7':
357 			case '9':
358 			case '0': // printf("%s\n", scratch);
359 				  shriek(474, "Unhandled response code");
360 			default : ;
361 		}
362 		printf("%s\n", scratch+strspn(scratch, "0123456789 "));
363 	}
364 	return 649;
365 }
366 
367 #undef ERROR_CODE
368