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