1: /*
2: * $Id: io_socket.c,v 1.9 2025/04/13 04:22:43 snw Exp $
3: * socket i/o support
4: *
5: *
6: * Author: Serena Willis <snw@coherent-logic.com>
7: * Copyright (C) 1998 MUG Deutschland
8: * Copyright (C) 2020, 2025 Coherent Logic Development LLC
9: *
10: *
11: * This file is part of FreeM.
12: *
13: * FreeM is free software: you can redistribute it and/or modify
14: * it under the terms of the GNU Affero Public License as published by
15: * the Free Software Foundation, either version 3 of the License, or
16: * (at your option) any later version.
17: *
18: * FreeM is distributed in the hope that it will be useful,
19: * but WITHOUT ANY WARRANTY; without even the implied warranty of
20: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21: * GNU Affero Public License for more details.
22: *
23: * You should have received a copy of the GNU Affero Public License
24: * along with FreeM. If not, see <https://www.gnu.org/licenses/>.
25: *
26: * $Log: io_socket.c,v $
27: * Revision 1.9 2025/04/13 04:22:43 snw
28: * Fix snprintf calls
29: *
30: * Revision 1.8 2025/04/10 01:24:38 snw
31: * Remove C++ style comments
32: *
33: * Revision 1.7 2025/03/24 02:56:15 snw
34: * Socket I/O compat fixes for OS/2
35: *
36: * Revision 1.6 2025/03/24 02:55:26 snw
37: * Socket I/O compat fixes for OS/2
38: *
39: * Revision 1.5 2025/03/24 02:54:11 snw
40: * Socket I/O compat fixes for OS/2
41: *
42: * Revision 1.4 2025/03/22 18:43:54 snw
43: * Make STRLEN 255 chars and add BIGSTR macro for larger buffers
44: *
45: * Revision 1.3 2025/03/09 19:14:25 snw
46: * First phase of REUSE compliance and header reformat
47: *
48: *
49: * SPDX-FileCopyrightText: (C) 2025 Coherent Logic Development LLC
50: * SPDX-License-Identifier: AGPL-3.0-or-later
51: **/
52:
53: #include <stdio.h>
54: #include <stdlib.h>
55: #include <string.h>
56: #include <ctype.h>
57: #include <signal.h>
58: #include <setjmp.h>
59:
60: #if !defined(MSDOS)
61: # include <sys/socket.h>
62: # include <arpa/inet.h>
63: # include <netdb.h>
64: #endif
65:
66: #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(_SCO_DS) && !defined(MSDOS)
67: # include <netinet/in.h>
68: #endif
69:
70: #if !defined(__OpenBSD__) && !defined(__FreeBSD__)
71: # include <sys/timeb.h>
72: #endif
73:
74: #if defined(__NetBSD__) || defined(__FreeBSD__) || defined(_SCO_DS)
75: # define USE_SYS_TIME_H
76: #endif
77:
78: #if defined(_SCO_DS)
79: # define SHUT_RDWR 2
80: #endif
81:
82: #ifdef USE_SYS_TIME_H
83: #include <sys/time.h>
84: #endif
85:
86: #if defined(__APPLE__)
87: # include <sys/select.h>
88: #endif
89:
90: #include "mpsdef.h"
91: #include "mref.h"
92: #include "io_socket.h"
93:
94: io_socket *io_sockets[MAXSCK];
95:
96: /* channel: channel number
97: addr_string: server:port[:family:[udp|tcp]]*/
98: short msck_open (int channel, char *addr_string)
99: {
100: #if !defined(MSDOS) && !defined(__OS2__)
101: char *addr = "";
102: char *port = "";
103: char *family = "";
104: char *typ = "";
105:
106: char *finaddr;
107:
108: struct hostent *he;
109: struct in_addr **addr_list;
110:
111: int pt = 0;
112: int fm = 0;
113: int tp = 0;
114:
115: short ct = 0;
116: register int j = 0;
117:
118: short i = channel + FIRSTSCK; /* get index into io_sockets[] array */
119:
120: finaddr = (char *) malloc (256 * sizeof (char));
121: NULLPTRCHK(finaddr,"msck_open");
122:
123:
124: merr_clear_iochan_err (channel);
125:
126: for (j = 0; j < strlen (addr_string); j++) {
127: if (addr_string[j] == ':') ct++;
128: }
129:
130: if (ct < 1 || ct > 3) {
131: merr_raise (ARGLIST);
132: merr_set_iochan_err (channel, ARGLIST, "invalid OPEN parameters");
133: return -1;
134: }
135:
136: addr = strtok (addr_string, ":");
137: port = strtok (NULL, ":");
138:
139: if (ct == 1) {
140:
141: family = (char *) malloc (256 * sizeof (char));
142: NULLPTRCHK(family,"msck_open");
143:
144: typ = (char *) malloc (256 * sizeof (char));
145: NULLPTRCHK(typ,"msck_open");
146:
147: strcpy (family, "IPV4");
148: strcpy (typ, "TCP");
149: }
150:
151: if (ct == 2) {
152: family = strtok (NULL, ":");
153:
154: typ = (char *) malloc (256 * sizeof (char));
155: NULLPTRCHK(typ,"msck_open");
156:
157: strcpy (typ, "TCP");
158: }
159:
160: if (ct == 3) {
161: family = strtok (NULL, ":");
162: typ = strtok (NULL, ":");
163: }
164:
165: if (strcmp (family, "IPV4") == 0) {
166: fm = AF_INET;
167: }
168: #if !defined(_SCO_DS) && defined(AF_INET6)
169: else if (strcmp (family, "IPV6") == 0) {
170: fm = AF_INET6;
171: }
172: #endif
173: else {
174: merr_raise (SCKIFAM);
175: merr_set_iochan_err (channel, SCKIFAM, "invalid address family");
176: return 0;
177: }
178:
179: if (strcmp (typ, "TCP") == 0) {
180: tp = SOCK_STREAM;
181: }
182: else if (strcmp (typ, "UDP") == 0) {
183: tp = SOCK_DGRAM;
184: }
185: else {
186: merr_raise (SCKITYP);
187: return 0;
188: }
189:
190: io_sockets[i] = (io_socket *) malloc (sizeof (io_socket));
191: NULLPTRCHK(io_sockets[i],"msck_open");
192:
193: io_sockets[i]->io_channel = channel;
194: io_sockets[i]->typ = tp;
195: io_sockets[i]->sck = socket (fm, tp, 0);
196: io_sockets[i]->srv.sin_family = fm;
197:
198: pt = atoi (port);
199:
200: if (pt < 0 || pt > 65535) {
201: merr_raise (SCKIPRT);
202: merr_set_iochan_err (channel, SCKIPRT, "invalid port number");
203: return 0;
204: }
205:
206: io_sockets[i]->srv.sin_port = htons (pt);
207:
208: #if !defined(INADDR_NONE)
209: # define INADDR_NONE -1
210: #endif
211:
212: if (inet_addr (addr) == INADDR_NONE) {
213:
214: /* addr is not a valid IP. we need to do a nameserver lookup. */
215:
216: if ((he = gethostbyname (addr)) == NULL) {
217: merr_raise (NAMERES);
218: merr_set_iochan_err (channel, NAMERES, "name resolution failure");
219: return 0;
220: }
221:
222: addr_list = (struct in_addr **) he->h_addr_list;
223:
224: strcpy (finaddr, inet_ntoa (*addr_list[0]));
225:
226: }
227: else {
228:
229: /* this was already a valid IP, so we can just use it as-is. */
230:
231: strcpy (finaddr, addr);
232:
233: }
234:
235: io_sockets[i]->srv.sin_addr.s_addr = inet_addr (finaddr);
236: io_sockets[i]->connected = FALSE;
237:
238: if (io_sockets[i]->sck == -1) {
239: merr_raise (SCKCREAT);
240: merr_set_iochan_err (channel, SCKCREAT, "error creating socket");
241: return 0;
242: }
243:
244: return i;
245:
246: #else
247:
248: merr_raise (SCKCREAT);
249: merr_set_iochan_err (channel, SCKCREAT, "error creating socket");
250: return 0;
251:
252: #endif
253:
254: }
255:
256: short msck_connect (int channel)
257: {
258:
259: #if !defined(MSDOS) && !defined(__OS2__)
260:
261: short i = channel + FIRSTSCK;
262:
263: if (io_sockets[i]->typ != SOCK_STREAM) {
264: merr_raise (SCKAERR);
265: merr_set_iochan_err (channel, SCKAERR, "cannot CONNECT a UDP socket");
266: return 0;
267: }
268:
269: if (io_sockets[i]->connected == TRUE) {
270: merr_raise (SCKACON);
271: merr_set_iochan_err (channel, SCKACON, "cannot CONNECT previously-connected socket");
272: return 0;
273: }
274:
275: if (connect (io_sockets[i]->sck, (struct sockaddr *) &(io_sockets[i]->srv), sizeof (io_sockets[i]->srv)) < 0) {
276: merr_raise (SCKCERR);
277: merr_set_iochan_err (channel, SCKCERR, "error in CONNECT");
278: return 0;
279: }
280: else {
281: io_sockets[i]->connected = TRUE;
282: }
283:
284: return i;
285:
286: #else
287:
288: return 0;
289:
290: #endif
291:
292: }
293:
294: short msck_write (int channel, char *buf, short length)
295: {
296:
297: #if !defined(MSDOS) && !defined(__OS2__)
298:
299: ssize_t ct;
300: short i = channel + FIRSTSCK;
301:
302: if (io_sockets[i]->connected == FALSE && io_sockets[i]->typ != SOCK_DGRAM) {
303:
304: /* throw socket not connected error if not doing UDP */
305: merr_raise (SCKNCON);
306: merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
307: return 0;
308:
309: }
310:
311: if ((ct = send (io_sockets[i]->sck, buf, length, 0)) < 0) {
312: merr_raise (SCKESND);
313: merr_set_iochan_err (channel, SCKESND, "error in WRITE to socket");
314: return 0;
315: }
316:
317: return ct;
318:
319: #else
320:
321: merr_raise (SCKNCON);
322: merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
323: return 0;
324:
325: #endif
326:
327: }
328:
329: short msck_read (int channel, char *buf, long sck_timeout, short sck_timeoutms, short length)
330: {
331:
332: #if !defined(MSDOS) && !defined(__OS2__)
333: fd_set fds;
334: short i;
335: struct timeval t;
336: char *terminator;
337: char *rdbuf;
338: char ch;
339: short in_term = 0;
340: short termlen = 0;
341: ssize_t rcvct = 0;
342: ssize_t ct = 0;
343:
344: i = channel + FIRSTSCK;
345: terminator = (char *) malloc (255 * sizeof (char));
346: NULLPTRCHK(terminator,"msck_read");
347:
348: rdbuf = (char *) malloc (length * sizeof (char));
349: NULLPTRCHK(rdbuf,"msck_read");
350:
351: if (io_sockets[i]->connected == FALSE && io_sockets[i]->typ != SOCK_DGRAM) {
352: merr_raise (SCKNCON);
353: merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
354: return 0;
355: }
356:
357: buf[0] = '\0';
358:
359: termlen = msck_get_terminator (channel, terminator);
360:
361: for (;;) {
362:
363: FD_ZERO (&fds);
364: FD_SET ((unsigned int) io_sockets[i]->sck, &fds);
365:
366: if (sck_timeout == -1) {
367: /* wait forever */
368: select (io_sockets[i]->sck + 1, &fds, NULL, NULL, NULL);
369: }
370: else {
371:
372: /* set the socket timeout */
373: t.tv_sec = sck_timeout;
374: t.tv_usec = sck_timeoutms * 1000;
375:
376: select (io_sockets[i]->sck + 1, &fds, NULL, NULL, &t);
377:
378: }
379:
380: if (ct >= length) goto read_done;
381:
382: if ((rcvct = recv (io_sockets[i]->sck, rdbuf, 1, 0)) < 1) {
383: merr_raise (SCKERCV);
384: merr_set_iochan_err (channel, SCKERCV, "error in READ from socket");
385: return 0;
386: }
387: else {
388:
389: ct++;
390:
391: ch = rdbuf[0];
392: rdbuf[1] = '\0';
393:
394: }
395:
396: strcat (buf, rdbuf);
397:
398: if (ch == terminator[0] && termlen == 1) {
399: /* 1-char terminator reached. populate $KEY. */
400: sprintf (zb, "%c\201", terminator[0]);
401: goto read_done;
402: }
403: else if (ch == terminator[0] && termlen == 2 && !in_term) {
404: /* first char of 2-char terminator found. we're now in a terminator. */
405: in_term = 1;
406: }
407: else if (ch == terminator[0] && termlen == 2 && in_term) {
408: /* we're in a 2-char terminator, but the second char doesn't match. */
409: in_term = 0;
410: }
411: else if (ch == terminator[1] && termlen == 2 && in_term) {
412: /* 2-char terminator reached. populate $KEY */
413: sprintf (zb, "%s\201", terminator);
414: goto read_done;
415: }
416:
417: }
418:
419: read_done:
420:
421: stcnv_c2m (buf);
422:
423: return ct;
424:
425: #else
426:
427: return 0;
428:
429: #endif
430:
431: }
432:
433: short msck_close (int channel)
434: {
435:
436: #if !defined(MSDOS) && !defined(__OS2__)
437: short i = channel + FIRSTSCK;
438:
439:
440: if (io_sockets[i] != NULL) {
441:
442: shutdown (io_sockets[i]->sck, SHUT_RDWR);
443:
444: free (io_sockets[i]);
445:
446: return 1;
447: }
448: #endif
449:
450: return 0;
451:
452: }
453:
454: short msck_get_terminator (int channel, char *buf)
455: {
456: char wr_io[9];
457: char *wr_key;
458:
459: freem_ref_t *wrr = (freem_ref_t *) malloc (sizeof (freem_ref_t));
460: NULLPTRCHK(wrr,"msck_get_terminator");
461:
462: snprintf (wr_io, sizeof (wr_io) - 1, "%d", channel);
463:
464: /* get ^$DEVICE($IO,"TERMINATOR") */
465:
466: mref_init (wrr, MREF_RT_SSV, "^$DEVICE");
467: mref_set_subscript (wrr, 0, wr_io);
468: mref_set_subscript (wrr, 1, "TERMINATOR");
469:
470: wr_key = mref_to_internal (wrr);
471:
472: ssvn (get_sym, wr_key, buf);
473:
474: if (merr () > OK) {
475:
476: /* SSV node was undefined. Default to CRLF terminator. */
477:
478: sprintf (buf, "\r\n\201");
479: merr_raise (OK);
480:
481: }
482:
483: stcnv_m2c (buf);
484:
485: free (wr_key);
486: free (wrr);
487:
488: return strlen (buf);
489: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>