stringbuf.c: random idle style cleanup.
[citadel.git] / citadel / clientsocket.c
1 // This module handles client-side sockets opened by the Citadel server (for
2 // the client side of Internet protocols, etc.)   It does _not_ handle client
3 // sockets for the Citadel client; for that you must look in ipc_c_tcp.c
4 // (which, uncoincidentally, bears a striking similarity to this file).
5 //
6 // Copyright (c) 1987-2017 by the citadel.org team
7 //
8 // This program is open source software.  Use, duplication, or disclosure
9 // is subject to the terms of the GNU General Public License, version 3.
10 // The program is distributed without any warranty, expressed or implied.
11
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <netdb.h>
15 #include <stdio.h>
16 #include <libcitadel.h>
17 #include "ctdl_module.h"
18 #include "clientsocket.h"
19
20 int sock_connect(char *host, char *service)
21 {
22         struct in6_addr serveraddr;
23         struct addrinfo hints;
24         struct addrinfo *res = NULL;
25         struct addrinfo *ai = NULL;
26         int rc = (-1);
27         int sock = (-1);
28
29         if ((host == NULL) || IsEmptyStr(host))
30                 return (-1);
31         if ((service == NULL) || IsEmptyStr(service))
32                 return (-1);
33
34         memset(&hints, 0x00, sizeof(hints));
35         hints.ai_flags = AI_NUMERICSERV;
36         hints.ai_family = AF_UNSPEC;
37         hints.ai_socktype = SOCK_STREAM;
38
39         /*
40          * Handle numeric IPv4 and IPv6 addresses
41          */
42         rc = inet_pton(AF_INET, host, &serveraddr);
43         if (rc == 1) {                                          /* dotted quad */
44                 hints.ai_family = AF_INET;
45                 hints.ai_flags |= AI_NUMERICHOST;
46         } else {
47                 rc = inet_pton(AF_INET6, host, &serveraddr);
48                 if (rc == 1) {                                  /* IPv6 address */
49                         hints.ai_family = AF_INET6;
50                         hints.ai_flags |= AI_NUMERICHOST;
51                 }
52         }
53
54         /* Begin the connection process */
55
56         rc = getaddrinfo(host, service, &hints, &res);
57         if (rc != 0) {
58                 syslog(LOG_ERR, "%s: %s", host, gai_strerror(rc));
59                 return(-1);
60         }
61
62         /*
63          * Try all available addresses until we connect to one or until we run out.
64          */
65         for (ai = res; ai != NULL; ai = ai->ai_next) {
66                 sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
67                 if (sock < 0) {
68                         syslog(LOG_ERR, "%s: %m", host);
69                         freeaddrinfo(res);
70                         return(-1);
71                 }
72                 rc = connect(sock, ai->ai_addr, ai->ai_addrlen);
73                 if (rc >= 0) {
74                         freeaddrinfo(res);
75                         return(sock);
76                 }
77                 else {
78                         syslog(LOG_ERR, "%s: %m", host);
79                         close(sock);
80                 }
81         }
82         freeaddrinfo(res);
83         return(-1);
84 }
85
86
87
88 /*
89  * Read data from the client socket.
90  *
91  * sock         socket fd to read from
92  * buf          buffer to read into 
93  * bytes        number of bytes to read
94  * timeout      Number of seconds to wait before timing out
95  *
96  * Possible return values:
97  *      1       Requested number of bytes has been read.
98  *      0       Request timed out.
99  *      -1      Connection is broken, or other error.
100  */
101 int socket_read_blob(int *Socket, StrBuf *Target, int bytes, int timeout)
102 {
103         const char *Error;
104         int retval = 0;
105
106         retval = StrBufReadBLOBBuffered(Target, CC->SBuf.Buf, &CC->SBuf.ReadWritePointer, Socket, 1, bytes, O_TERM, &Error); 
107         if (retval < 0) {
108                 syslog(LOG_ERR, "clientsocket: socket_read_blob() failed: %s", Error);
109         }
110         return retval;
111 }
112
113
114 int CtdlSockGetLine(int *sock, StrBuf *Target, int nSec)
115 {
116         CitContext *CCC = MyContext();
117         const char *Error;
118         int rc;
119
120         FlushStrBuf(Target);
121         rc = StrBufTCP_read_buffered_line_fast(Target,
122                                                CCC->SBuf.Buf,
123                                                &CCC->SBuf.ReadWritePointer,
124                                                sock, nSec, 1, &Error);
125         if ((rc < 0) && (Error != NULL)) {
126                 syslog(LOG_ERR, "clientsocket: CtdlSockGetLine() failed: %s", Error);
127         }
128         return rc;
129 }
130
131
132 /*
133  * client_getln()   ...   Get a LF-terminated line of text from the client.
134  */
135 int sock_getln(int *sock, char *buf, int bufsize)
136 {
137         int i, retval;
138         CitContext *CCC = MyContext();
139         const char *pCh;
140
141         FlushStrBuf(CCC->sMigrateBuf);
142         retval = CtdlSockGetLine(sock, CCC->sMigrateBuf, 5);
143
144         i = StrLength(CCC->sMigrateBuf);
145         pCh = ChrPtr(CCC->sMigrateBuf);
146
147         memcpy(buf, pCh, i + 1);
148
149         FlushStrBuf(CCC->sMigrateBuf);
150         if (retval < 0) {
151                 safestrncpy(&buf[i], "000", bufsize - i);
152                 i += 3;
153         }
154         return i;
155 }
156
157
158 /*
159  * sock_write() - send binary to server.
160  * Returns the number of bytes written, or -1 for error.
161  */
162 int sock_write(int *sock, const char *buf, int nbytes) 
163 { return sock_write_timeout(sock, buf, nbytes, 50); }
164 int sock_write_timeout(int *sock, const char *buf, int nbytes, int timeout)
165 {
166         int nSuccessLess = 0;
167         int bytes_written = 0;
168         int retval;
169         fd_set rfds;
170         int fdflags;
171         int IsNonBlock;
172         struct timeval tv;
173         int selectresolution = 100;
174
175         fdflags = fcntl(*sock, F_GETFL);
176         IsNonBlock = (fdflags & O_NONBLOCK) == O_NONBLOCK;
177
178         while ((nSuccessLess < timeout) && 
179                (*sock != -1) && 
180                (bytes_written < nbytes)) 
181         {
182                 if (IsNonBlock){
183                         tv.tv_sec = selectresolution;
184                         tv.tv_usec = 0;
185                         
186                         FD_ZERO(&rfds);
187                         FD_SET(*sock, &rfds);
188                         if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
189                                 close (*sock);
190                                 *sock = -1;
191                                 return -1;
192                         }
193                 }
194                 if (IsNonBlock && !  FD_ISSET(*sock, &rfds)) {
195                         nSuccessLess ++;
196                         continue;
197                 }
198                 retval = write(*sock, &buf[bytes_written],
199                                nbytes - bytes_written);
200                 if (retval < 1) {
201                         sock_close(*sock);
202                         *sock = -1;
203                         return (-1);
204                 }
205                 bytes_written = bytes_written + retval;
206                 if (IsNonBlock && (bytes_written == nbytes)){
207                         tv.tv_sec = selectresolution;
208                         tv.tv_usec = 0;
209                         
210                         FD_ZERO(&rfds);
211                         FD_SET(*sock, &rfds);
212                         if (select(*sock + 1, NULL, &rfds, NULL, &tv) == -1) {
213                                 close (*sock);
214                                 *sock = -1;
215                                 return -1;
216                         }
217                 }
218         }
219         return (bytes_written);
220 }
221
222
223 /*
224  * client_getln()   ...   Get a LF-terminated line of text from the client.
225  */
226 int sock_getln_err(int *sock, char *buf, int bufsize, int *rc, int nSec)
227 {
228         int i, retval;
229         CitContext *CCC = MyContext();
230         const char *pCh;
231
232         FlushStrBuf(CCC->sMigrateBuf);
233         *rc = retval = CtdlSockGetLine(sock, CCC->sMigrateBuf, nSec);
234
235         i = StrLength(CCC->sMigrateBuf);
236         pCh = ChrPtr(CCC->sMigrateBuf);
237
238         memcpy(buf, pCh, i + 1);
239
240         FlushStrBuf(CCC->sMigrateBuf);
241         if (retval < 0) {
242                 safestrncpy(&buf[i], "000", bufsize - i);
243                 i += 3;
244         }
245         return i;
246 }
247
248
249 /*
250  * Multiline version of sock_gets() ... this is a convenience function for
251  * client side protocol implementations.  It only returns the first line of
252  * a multiline response, discarding the rest.
253  */
254 int ml_sock_gets(int *sock, char *buf, int nSec)
255 {
256         int rc = 0;
257         char bigbuf[1024];
258         int g;
259
260         g = sock_getln_err(sock, buf, SIZ, &rc, nSec);
261         if (rc < 0)
262                 return rc;
263         if (g < 4)
264                 return (g);
265         if (buf[3] != '-')
266                 return (g);
267
268         do {
269                 g = sock_getln_err(sock, bigbuf, SIZ, &rc, nSec);
270                 if (rc < 0)
271                         return rc;
272                 if (g < 0)
273                         return (g);
274         } while ((g >= 4) && (bigbuf[3] == '-'));
275
276         return (strlen(buf));
277 }
278
279
280 /*
281  * sock_puts() - send line to server - implemented in terms of serv_write()
282  * Returns the number of bytes written, or -1 for error.
283  */
284 int sock_puts(int *sock, char *buf)
285 {
286         int i, j;
287
288         i = sock_write(sock, buf, strlen(buf));
289         if (i < 0)
290                 return (i);
291         j = sock_write(sock, "\n", 1);
292         if (j < 0)
293                 return (j);
294         return (i + j);
295 }