1 | /* |
2 | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
5 | * |
6 | * Copyright 1997 - July 2008 CWI, August 2008 - 2019 MonetDB B.V. |
7 | */ |
8 | |
9 | #include "monetdb_config.h" |
10 | |
11 | #include <string.h> /* strerror, strchr, strcmp */ |
12 | #include <sys/types.h> |
13 | #include <sys/socket.h> |
14 | #include <sys/un.h> |
15 | #include <netdb.h> |
16 | #include <netinet/in.h> |
17 | #ifdef HAVE_POLL_H |
18 | #include <poll.h> |
19 | #endif |
20 | #ifdef HAVE_SYS_UIO_H |
21 | # include <sys/uio.h> |
22 | #endif |
23 | #include <fcntl.h> |
24 | |
25 | #include "msabaoth.h" |
26 | #include "mcrypt.h" |
27 | #include "stream.h" |
28 | #include "stream_socket.h" |
29 | #include "utils/utils.h" /* freeConfFile */ |
30 | #include "utils/properties.h" /* readProps */ |
31 | |
32 | #include "merovingian.h" |
33 | #include "forkmserver.h" |
34 | #include "proxy.h" |
35 | #include "multiplex-funnel.h" |
36 | #include "controlrunner.h" |
37 | #include "client.h" |
38 | #include "handlers.h" |
39 | |
40 | #if !defined(HAVE_ACCEPT4) || !defined(SOCK_CLOEXEC) |
41 | #define accept4(sockfd, addr, addrlen, flags) accept(sockfd, addr, addrlen) |
42 | #endif |
43 | |
44 | struct threads { |
45 | struct threads *next; |
46 | pthread_t tid; |
47 | volatile char dead; |
48 | }; |
49 | struct clientdata { |
50 | int sock; |
51 | bool isusock; |
52 | struct threads *self; |
53 | char challenge[32]; |
54 | }; |
55 | |
56 | static void * |
57 | handleClient(void *data) |
58 | |
59 | { |
60 | stream *fdin, *fout; |
61 | char buf[8096]; |
62 | char chal[32]; |
63 | char *user = NULL, *algo = NULL, *passwd = NULL, *lang = NULL; |
64 | char *database = NULL, *s; |
65 | char dbmod[64]; |
66 | char host[512]; |
67 | char port[16]; |
68 | sabdb *top = NULL; |
69 | sabdb *stat = NULL; |
70 | struct sockaddr saddr; |
71 | socklen_t saddrlen = 0; |
72 | err e; |
73 | confkeyval *ckv, *kv; |
74 | char mydoproxy; |
75 | sabdb redirs[24]; /* do we need more? */ |
76 | int r = 0; |
77 | int sock; |
78 | bool isusock; |
79 | struct threads *self; |
80 | |
81 | sock = ((struct clientdata *) data)->sock; |
82 | isusock = ((struct clientdata *) data)->isusock; |
83 | self = ((struct clientdata *) data)->self; |
84 | memcpy(chal, ((struct clientdata *) data)->challenge, sizeof(chal)); |
85 | free(data); |
86 | fdin = socket_rstream(sock, "merovingian<-client (read)" ); |
87 | if (fdin == 0) { |
88 | self->dead = 1; |
89 | return(newErr("merovingian-client inputstream problems" )); |
90 | } |
91 | fdin = block_stream(fdin); |
92 | |
93 | fout = socket_wstream(sock, "merovingian->client (write)" ); |
94 | if (fout == 0) { |
95 | close_stream(fdin); |
96 | self->dead = 1; |
97 | return(newErr("merovingian-client outputstream problems" )); |
98 | } |
99 | fout = block_stream(fout); |
100 | |
101 | if (isusock) { |
102 | snprintf(host, sizeof(host), "(local)" ); |
103 | } else if (getpeername(sock, &saddr, &saddrlen) == -1) { |
104 | Mfprintf(stderr, "couldn't get peername of client: %s\n" , strerror(errno)); |
105 | snprintf(host, sizeof(host), "(unknown)" ); |
106 | } else { |
107 | char ghost[512]; |
108 | if (getnameinfo(&saddr, saddrlen, ghost, sizeof(ghost), port, sizeof(port), |
109 | NI_NUMERICSERV | NI_NUMERICHOST) == 0) { |
110 | snprintf(host, sizeof(host), "%s:%s" , ghost, port); |
111 | } else { |
112 | snprintf(host, sizeof(host), "(unknown):%s" , port); |
113 | } |
114 | } |
115 | |
116 | /* note: since Jan2012 we speak proto 9 for control connections */ |
117 | mnstr_printf(fout, "%s:merovingian:9:%s:%s:%s:" , |
118 | chal, |
119 | mcrypt_getHashAlgorithms(), |
120 | #ifdef WORDS_BIGENDIAN |
121 | "BIG" , |
122 | #else |
123 | "LIT" , |
124 | #endif |
125 | MONETDB5_PASSWDHASH |
126 | ); |
127 | mnstr_flush(fout); |
128 | |
129 | /* get response */ |
130 | buf[0] = '\0'; |
131 | if (mnstr_read_block(fdin, buf, sizeof(buf) - 1, 1) < 0) { |
132 | /* we didn't get a terminated block :/ */ |
133 | e = newErr("client %s sent challenge in incomplete block: %s" , |
134 | host, buf); |
135 | mnstr_printf(fout, "!monetdbd: client sent something this " |
136 | "server could not understand, sorry\n" ); |
137 | mnstr_flush(fout); |
138 | close_stream(fout); |
139 | close_stream(fdin); |
140 | self->dead = 1; |
141 | return(e); |
142 | } |
143 | buf[sizeof(buf) - 1] = '\0'; |
144 | |
145 | /* decode BIG/LIT:user:{cypher}passwordchal:lang:database: line */ |
146 | |
147 | user = buf; |
148 | /* byte order */ |
149 | s = strchr(user, ':'); |
150 | if (s) { |
151 | *s = 0; |
152 | /* we don't use this in merovingian */ |
153 | /* mnstr_set_byteorder(fin->s, strcmp(user, "BIG") == 0); */ |
154 | user = s + 1; |
155 | } else { |
156 | e = newErr("client %s challenge error: %s" , host, buf); |
157 | mnstr_printf(fout, "!monetdbd: incomplete challenge '%s'\n" , buf); |
158 | mnstr_flush(fout); |
159 | close_stream(fout); |
160 | close_stream(fdin); |
161 | self->dead = 1; |
162 | return(e); |
163 | } |
164 | |
165 | /* passwd */ |
166 | s = strchr(user, ':'); |
167 | if (s) { |
168 | *s = 0; |
169 | passwd = s + 1; |
170 | /* decode algorithm, i.e. {plain}mypasswordchallenge */ |
171 | if (*passwd != '{') { |
172 | e = newErr("client %s challenge error: %s" , host, buf); |
173 | mnstr_printf(fout, "!monetdbd: invalid password entry\n" ); |
174 | mnstr_flush(fout); |
175 | close_stream(fout); |
176 | close_stream(fdin); |
177 | self->dead = 1; |
178 | return(e); |
179 | } |
180 | algo = passwd + 1; |
181 | s = strchr(algo, '}'); |
182 | if (!s) { |
183 | e = newErr("client %s challenge error: %s" , host, buf); |
184 | mnstr_printf(fout, "!monetdbd: invalid password entry\n" ); |
185 | mnstr_flush(fout); |
186 | close_stream(fout); |
187 | close_stream(fdin); |
188 | self->dead = 1; |
189 | return(e); |
190 | } |
191 | *s = 0; |
192 | passwd = s + 1; |
193 | } else { |
194 | e = newErr("client %s challenge error: %s" , host, buf); |
195 | mnstr_printf(fout, "!monetdbd: incomplete challenge, missing password after '%s'\n" , user); |
196 | mnstr_flush(fout); |
197 | close_stream(fout); |
198 | close_stream(fdin); |
199 | self->dead = 1; |
200 | return(e); |
201 | } |
202 | |
203 | /* lang */ |
204 | s = strchr(passwd, ':'); |
205 | if (s) { |
206 | *s = 0; |
207 | lang = s + 1; |
208 | } else { |
209 | e = newErr("client %s challenge error: %s" , host, buf); |
210 | mnstr_printf(fout, "!monetdbd: incomplete challenge, missing language after '%s'\n" , passwd); |
211 | mnstr_flush(fout); |
212 | close_stream(fout); |
213 | close_stream(fdin); |
214 | self->dead = 1; |
215 | return(e); |
216 | } |
217 | |
218 | /* database */ |
219 | s = strchr(lang, ':'); |
220 | if (s) { |
221 | *s = 0; |
222 | database = s + 1; |
223 | /* since we don't know where the string ends, we need to look |
224 | * for another : */ |
225 | s = strchr(database, ':'); |
226 | if (s == NULL) { |
227 | e = newErr("client %s challenge error: %s" , host, buf); |
228 | mnstr_printf(fout, "!monetdbd: incomplete challenge, missing trailing colon\n" ); |
229 | mnstr_flush(fout); |
230 | close_stream(fout); |
231 | close_stream(fdin); |
232 | self->dead = 1; |
233 | return(e); |
234 | } else { |
235 | *s = '\0'; |
236 | } |
237 | } |
238 | |
239 | if (database == NULL || *database == '\0') { |
240 | /* we need to have a database, if we haven't gotten one, |
241 | * complain */ |
242 | mnstr_printf(fout, "!monetdbd: please specify a database\n" ); |
243 | mnstr_flush(fout); |
244 | close_stream(fout); |
245 | close_stream(fdin); |
246 | self->dead = 1; |
247 | return(newErr("client %s specified no database" , host)); |
248 | } |
249 | |
250 | if (strcmp(lang, "control" ) == 0) { |
251 | /* handle control client */ |
252 | if (control_authorise(host, chal, algo, passwd, fout)) |
253 | control_handleclient(host, sock, fdin, fout); |
254 | close_stream(fout); |
255 | close_stream(fdin); |
256 | self->dead = 1; |
257 | return(NO_ERR); |
258 | } |
259 | |
260 | if (strcmp(lang, "resolve" ) == 0) { |
261 | /* ensure the pattern ends with '/\*' such that we force a |
262 | * remote entry, including those for local databases, this |
263 | * way we will get a redirect back to merovingian for such |
264 | * database if it is proxied and hence not remotely |
265 | * available */ |
266 | size_t len = strlen(database); |
267 | if (len > 2 && |
268 | database[len - 2] != '/' && |
269 | database[len - 1] != '*') |
270 | { |
271 | snprintf(dbmod, sizeof(dbmod), "%s/*" , database); |
272 | database = dbmod; |
273 | } |
274 | } |
275 | |
276 | if ((e = forkMserver(database, &top, false)) != NO_ERR) { |
277 | if (top == NULL) { |
278 | mnstr_printf(fout, "!monetdbd: no such database '%s', please create it first\n" , database); |
279 | } else { |
280 | mnstr_printf(fout, "!monetdbd: internal error while starting mserver '%s'%s\n" , e, strstr(e, "logfile" )?"" :", please refer to the logs" ); |
281 | Mfprintf(_mero_ctlerr, "!monetdbd: an internal error has occurred '%s'\n" ,e); |
282 | } |
283 | mnstr_flush(fout); |
284 | close_stream(fout); |
285 | close_stream(fdin); |
286 | self->dead = 1; |
287 | return(e); |
288 | } |
289 | stat = top; |
290 | |
291 | /* a multiplex-funnel is a database which has no connections, but a |
292 | * scenario "mfunnel" */ |
293 | if ((top->conns == NULL || top->conns->val == NULL) && |
294 | top->scens != NULL && strcmp(top->scens->val, "mfunnel" ) == 0) |
295 | { |
296 | multiplexAddClient(top->dbname, sock, fout, fdin, host); |
297 | msab_freeStatus(&top); |
298 | self->dead = 1; |
299 | return(NO_ERR); |
300 | } |
301 | |
302 | /* collect possible redirects */ |
303 | for (stat = top; stat != NULL; stat = stat->next) { |
304 | if (stat->conns == NULL || stat->conns->val == NULL) { |
305 | Mfprintf(stdout, "dropping database without available " |
306 | "connections: '%s'\n" , stat->dbname); |
307 | } else if (r == 24) { |
308 | Mfprintf(stdout, "dropping database connection because of " |
309 | "too many already: %s\n" , stat->conns->val); |
310 | } else { |
311 | redirs[r++] = *stat; |
312 | } |
313 | } |
314 | |
315 | /* if we can't redirect, our mission ends here */ |
316 | if (r == 0) { |
317 | if (top->locked) { |
318 | e = newErr("database '%s' is under maintenance" , top->dbname); |
319 | } else { |
320 | e = newErr("there are no available connections for '%s'" , database); |
321 | } |
322 | mnstr_printf(fout, "!monetdbd: %s\n" , e); |
323 | mnstr_flush(fout); |
324 | close_stream(fout); |
325 | close_stream(fdin); |
326 | msab_freeStatus(&top); |
327 | self->dead = 1; |
328 | return(e); |
329 | } |
330 | |
331 | /* need to send a response, either we are going to proxy, or we send |
332 | * a redirect, if we have multiple options, a redirect is our only |
333 | * option, but if the redir is a single remote we need to stick to |
334 | * our default, there is a special case when the client indicates it |
335 | * is only resolving a pattern, in which we always need to send |
336 | * redirects, even if it's one */ |
337 | mydoproxy = 0; |
338 | if (r == 1 && strcmp(lang, "resolve" ) != 0) { |
339 | if (redirs[0].dbname != redirs[0].path) { |
340 | /* this is a real local database (not a remote) */ |
341 | ckv = getDefaultProps(); |
342 | readProps(ckv, redirs[0].path); |
343 | kv = findConfKey(ckv, "forward" ); |
344 | } else { |
345 | ckv = NULL; |
346 | kv = NULL; |
347 | } |
348 | if (kv == NULL || kv->val == NULL) |
349 | kv = findConfKey(_mero_props, "forward" ); |
350 | mydoproxy = strcmp(kv->val, "proxy" ) == 0; |
351 | if (ckv != NULL) { |
352 | freeConfFile(ckv); |
353 | free(ckv); |
354 | } |
355 | } |
356 | |
357 | if (mydoproxy == 0) { |
358 | fprintf(stdout, "redirecting client %s for database '%s' to" , |
359 | host, database); |
360 | /* client is in control, send all redirects */ |
361 | while (--r >= 0) { |
362 | fprintf(stdout, " %s%s" , |
363 | redirs[r].conns->val, redirs[r].dbname); |
364 | mnstr_printf(fout, "^%s%s\n" , |
365 | redirs[r].conns->val, redirs[r].dbname); |
366 | } |
367 | /* flush redirect */ |
368 | fprintf(stdout, "\n" ); |
369 | fflush(stdout); |
370 | mnstr_flush(fout); |
371 | } else { |
372 | Mfprintf(stdout, "proxying client %s for database '%s' to " |
373 | "%s?database=%s\n" , |
374 | host, database, redirs[0].conns->val, redirs[0].dbname); |
375 | /* merovingian is in control, only consider the first redirect */ |
376 | mnstr_printf(fout, "^mapi:merovingian://proxy?database=%s\n" , |
377 | redirs[0].dbname); |
378 | /* flush redirect */ |
379 | mnstr_flush(fout); |
380 | |
381 | /* wait for input, or disconnect in a proxy runner */ |
382 | if ((e = startProxy(sock, fdin, fout, |
383 | redirs[0].conns->val, host)) != NO_ERR) |
384 | { |
385 | /* we need to let the client login in order not to violate |
386 | * the protocol */ |
387 | mnstr_printf(fout, "void:merovingian:9:%s:BIG:%s:" , |
388 | mcrypt_getHashAlgorithms(), MONETDB5_PASSWDHASH); |
389 | mnstr_flush(fout); |
390 | mnstr_read_block(fdin, buf, 8095, 1); /* eat away client response */ |
391 | mnstr_printf(fout, "!monetdbd: an internal error has occurred '%s', refer to the logs for details, please try again later\n" ,e); |
392 | mnstr_flush(fout); |
393 | Mfprintf(_mero_ctlerr, "!monetdbd: an internal error has occurred '%s'\n" ,e); |
394 | close_stream(fout); |
395 | close_stream(fdin); |
396 | Mfprintf(stdout, "starting a proxy failed: %s\n" , e); |
397 | msab_freeStatus(&top); |
398 | self->dead = 1; |
399 | return(e); |
400 | } |
401 | } |
402 | |
403 | msab_freeStatus(&top); |
404 | self->dead = 1; |
405 | return(NO_ERR); |
406 | } |
407 | |
408 | char * |
409 | acceptConnections(int sock, int usock) |
410 | { |
411 | char *msg; |
412 | int retval; |
413 | #ifdef HAVE_POLL |
414 | struct pollfd pfd[2]; |
415 | #else |
416 | fd_set fds; |
417 | struct timeval tv; |
418 | #endif |
419 | int msgsock; |
420 | void *e; |
421 | struct clientdata *data; |
422 | struct threads *threads = NULL, **threadp, *p; |
423 | int errnr; /* saved errno */ |
424 | |
425 | do { |
426 | /* handle socket connections */ |
427 | bool isusock = false; |
428 | |
429 | #ifdef HAVE_POLL |
430 | pfd[0] = (struct pollfd) {.fd = sock, .events = POLLIN}; |
431 | pfd[1] = (struct pollfd) {.fd = usock, .events = POLLIN}; |
432 | |
433 | /* Wait up to 5 seconds */ |
434 | retval = poll(pfd, 2, 5000); |
435 | #else |
436 | FD_ZERO(&fds); |
437 | FD_SET(sock, &fds); |
438 | FD_SET(usock, &fds); |
439 | |
440 | /* Wait up to 5 seconds */ |
441 | tv.tv_sec = 5; |
442 | tv.tv_usec = 0; |
443 | retval = select((sock > usock ? sock : usock) + 1, |
444 | &fds, NULL, NULL, &tv); |
445 | #endif |
446 | errnr = errno; |
447 | /* join any handleClient threads that we started and that may |
448 | * have finished by now */ |
449 | for (threadp = &threads; *threadp; threadp = &(*threadp)->next) { |
450 | if ((*threadp)->dead && |
451 | pthread_join((*threadp)->tid, &e) == 0) { |
452 | p = *threadp; |
453 | *threadp = p->next; |
454 | free(p); |
455 | if (e != NO_ERR) { |
456 | Mfprintf(stderr, "client error: %s\n" , |
457 | getErrMsg((char *) e)); |
458 | freeErr(e); |
459 | } |
460 | if (*threadp == NULL) |
461 | break; |
462 | } |
463 | } |
464 | childhandler(); |
465 | reinitialize(); |
466 | if (retval == 0) { |
467 | /* nothing interesting has happened */ |
468 | continue; |
469 | } |
470 | if (retval == -1) { |
471 | if (_mero_keep_listening == 0) |
472 | break; |
473 | switch (errnr) { |
474 | case EINTR: |
475 | /* interrupted */ |
476 | break; |
477 | case EMFILE: |
478 | case ENFILE: |
479 | case ENOBUFS: |
480 | case ENOMEM: |
481 | /* transient failures */ |
482 | break; |
483 | default: |
484 | msg = strerror(errnr); |
485 | goto error; |
486 | } |
487 | continue; |
488 | } |
489 | if ( |
490 | #ifdef HAVE_POLL |
491 | pfd[0].revents & POLLIN |
492 | #else |
493 | FD_ISSET(sock, &fds) |
494 | #endif |
495 | ) { |
496 | isusock = false; |
497 | if ((msgsock = accept4(sock, (SOCKPTR)0, (socklen_t *) 0, SOCK_CLOEXEC)) == -1) { |
498 | if (_mero_keep_listening == 0) |
499 | break; |
500 | switch (errno) { |
501 | case EINTR: |
502 | /* interrupted */ |
503 | break; |
504 | case EMFILE: |
505 | case ENFILE: |
506 | case ENOBUFS: |
507 | case ENOMEM: |
508 | /* transient failures */ |
509 | break; |
510 | case ECONNABORTED: |
511 | /* connection aborted before we began */ |
512 | break; |
513 | default: |
514 | msg = strerror(errno); |
515 | goto error; |
516 | } |
517 | continue; |
518 | } |
519 | #if defined(HAVE_FCNTL) && (!defined(SOCK_CLOEXEC) || !defined(HAVE_ACCEPT4)) |
520 | (void) fcntl(msgsock, F_SETFD, FD_CLOEXEC); |
521 | #endif |
522 | } else if ( |
523 | #ifdef HAVE_POLL |
524 | pfd[1].revents & POLLIN |
525 | #else |
526 | FD_ISSET(usock, &fds) |
527 | #endif |
528 | ) { |
529 | struct msghdr msgh; |
530 | struct iovec iov; |
531 | char buf[1]; |
532 | int rv; |
533 | char ccmsg[CMSG_SPACE(sizeof(int))]; |
534 | |
535 | isusock = true; |
536 | if ((msgsock = accept4(usock, (SOCKPTR)0, (socklen_t *)0, SOCK_CLOEXEC)) == -1) { |
537 | if (_mero_keep_listening == 0) |
538 | break; |
539 | switch (errno) { |
540 | case EINTR: |
541 | /* interrupted */ |
542 | break; |
543 | case EMFILE: |
544 | case ENFILE: |
545 | case ENOBUFS: |
546 | case ENOMEM: |
547 | /* transient failures */ |
548 | break; |
549 | case ECONNABORTED: |
550 | /* connection aborted before we began */ |
551 | break; |
552 | default: |
553 | msg = strerror(errno); |
554 | goto error; |
555 | } |
556 | continue; |
557 | } |
558 | #if defined(HAVE_FCNTL) && (!defined(SOCK_CLOEXEC) || !defined(HAVE_ACCEPT4)) |
559 | (void) fcntl(usock, F_SETFD, FD_CLOEXEC); |
560 | #endif |
561 | |
562 | /* BEWARE: unix domain sockets have a slightly different |
563 | * behaviour initialy than normal sockets, because we can |
564 | * send filedescriptors or credentials with them. To do so, |
565 | * we need to use sendmsg/recvmsg, which operates on a bare |
566 | * socket. Unfortunately we *have* to send something, so it |
567 | * is one byte that can optionally carry the ancillary data. |
568 | * This byte is at this moment defined to contain a character: |
569 | * '0' - there is no ancillary data |
570 | * '1' - ancillary data for passing a file descriptor |
571 | * The future may introduce a state for passing credentials. |
572 | * Any unknown character must be interpreted as some unknown |
573 | * action, and hence not supported by the server. |
574 | * Since there is no reason why one would like to pass |
575 | * descriptors to Merovingian, this is not implemented here. */ |
576 | |
577 | iov.iov_base = buf; |
578 | iov.iov_len = 1; |
579 | |
580 | msgh.msg_name = 0; |
581 | msgh.msg_namelen = 0; |
582 | msgh.msg_iov = &iov; |
583 | msgh.msg_iovlen = 1; |
584 | msgh.msg_control = ccmsg; |
585 | msgh.msg_controllen = sizeof(ccmsg); |
586 | |
587 | rv = recvmsg(msgsock, &msgh, 0); |
588 | if (rv == -1) { |
589 | closesocket(msgsock); |
590 | continue; |
591 | } |
592 | |
593 | switch (buf[0]) { |
594 | case '0': |
595 | /* nothing special, nothing to do */ |
596 | break; |
597 | case '1': |
598 | /* filedescriptor, no way */ |
599 | closesocket(msgsock); |
600 | Mfprintf(stderr, "client error: fd passing not supported\n" ); |
601 | continue; |
602 | default: |
603 | /* some unknown state */ |
604 | closesocket(msgsock); |
605 | Mfprintf(stderr, "client error: unknown initial byte\n" ); |
606 | continue; |
607 | } |
608 | } else |
609 | continue; |
610 | /* start handleClient as a thread so that we're not blocked by |
611 | * a slow client */ |
612 | data = malloc(sizeof(*data)); /* freed by handleClient */ |
613 | p = malloc(sizeof(*p)); |
614 | if (data == NULL || p == NULL) { |
615 | if (data) |
616 | free(data); |
617 | if (p) |
618 | free(p); |
619 | closesocket(msgsock); |
620 | Mfprintf(stderr, "cannot allocate memory\n" ); |
621 | continue; |
622 | } |
623 | data->sock = msgsock; |
624 | data->isusock = isusock; |
625 | p->dead = 0; |
626 | data->self = p; |
627 | data->challenge[31] = '\0'; |
628 | generateSalt(data->challenge, 31); |
629 | if (pthread_create(&p->tid, NULL, handleClient, data) == 0) { |
630 | p->next = threads; |
631 | threads = p; |
632 | } else { |
633 | closesocket(msgsock); |
634 | free(data); |
635 | free(p); |
636 | } |
637 | } while (_mero_keep_listening); |
638 | shutdown(sock, SHUT_RDWR); |
639 | closesocket(sock); |
640 | return(NO_ERR); |
641 | |
642 | error: |
643 | _mero_keep_listening = 0; |
644 | closesocket(sock); |
645 | return(newErr("accept connection: %s" , msg)); |
646 | } |
647 | |
648 | /* vim:set ts=4 sw=4 noexpandtab: */ |
649 | |