1 /* Copyright (C) 2002 J.F. Dockes
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU Lesser General Public License as published by
4  *   the Free Software Foundation; either version 2.1 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU Lesser General Public License for more details.
11  *
12  *   You should have received a copy of the GNU Lesser General Public License
13  *   along with this program; if not, write to the
14  *   Free Software Foundation, Inc.,
15  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16  */
17 
18 // Wrapper classes for the socket interface
19 
20 #ifdef BUILDING_RECOLL
21 #include "autoconfig.h"
22 #else
23 #include "config.h"
24 #endif
25 
26 #include "netcon.h"
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <stdint.h>
33 
34 #ifdef _AIX
35 #include <strings.h>
36 #endif // _AIX
37 
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <sys/socket.h>
41 #include <sys/un.h>
42 #include <netinet/in.h>
43 #include <netinet/tcp.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #ifdef HAVE_KQUEUE
47 #include <sys/types.h>
48 #include <sys/event.h>
49 #include <vector>
50 #endif
51 
52 #include <map>
53 #include <algorithm>
54 
55 #ifdef MDU_INCLUDE_LOG
56 #include MDU_INCLUDE_LOG
57 #else
58 #include "log.h"
59 #endif
60 
61 using namespace std;
62 
63 #ifndef PRETEND_USE
64 #define PRETEND_USE(expr) ((void)(expr))
65 #endif
66 
67 #ifndef SOCKLEN_T
68 #define SOCKLEN_T socklen_t
69 #endif
70 
71 // Size of path buffer in sockaddr_un (AF_UNIX socket
72 // addr). Mysteriously it is 108 (explicit value) under linux, no
73 // define accessible. Let's take a little margin as it appears that
74 // some systems use 92. I believe we could also malloc a variable size
75 // struct but why bother.
76 #ifndef UNIX_PATH_MAX
77 #define UNIX_PATH_MAX 90
78 #endif
79 
80 // Need &one, &zero for setsockopt...
81 static const int one = 1;
82 static const int zero = 0;
83 
84 #ifndef LOGSYSERR
85 #define LOGSYSERR(who, call, spar)                                \
86     LOGERR(who << ": "  << call << "("  << spar << ") errno " <<  \
87            errno << " ("  << strerror(errno) << ")\n")
88 #endif
89 
90 #ifndef freeZ
91 #define freeZ(X) if (X) {free(X);X=0;}
92 #endif
93 
94 #define MILLIS(OLD, NEW) ( (uint64_t((NEW).tv_sec) - (OLD).tv_sec) * 1000 + \
95                             ((NEW).tv_usec - (OLD).tv_usec) / 1000 )
96 
97 // Static method
98 // Simplified interface to 'select()'. Only use one fd, for either
99 // reading or writing. This is only used when not using the
100 // selectloop() style of network i/o.
101 // Note that timeo == 0 does NOT mean wait forever but no wait at all.
select1(int fd,int timeo,int write)102 int Netcon::select1(int fd, int timeo, int write)
103 {
104     int ret;
105     struct timeval tv;
106     fd_set rd;
107     tv.tv_sec = timeo;
108     tv.tv_usec =  0;
109     FD_ZERO(&rd);
110     FD_SET(fd, &rd);
111     if (write) {
112         ret = select(fd + 1, 0, &rd, 0, &tv);
113     } else {
114         ret = select(fd + 1, &rd, 0, 0, &tv);
115     }
116     if (!FD_ISSET(fd, &rd)) {
117         LOGDEB2("Netcon::select1: fd " << fd << " timeout\n");
118     }
119     return ret;
120 }
121 
122 
123 ///////////////////////////////////////////
124 // SelectLoop
125 
126 class SelectLoop::Internal {
127 public:
Internal()128     Internal() {
129 #ifdef HAVE_KQUEUE
130         if ((kq = kqueue()) == -1) {
131             LOGSYSERR("Netcon::selectloop", "kqueue", "");
132         }
133 #endif
134     }
135 
~Internal()136     ~Internal() {
137 #ifdef HAVE_KQUEUE
138         if (kq >= 0)
139             close(kq);
140 #endif
141     }
142 
143     // Set by client callback to tell selectloop to return.
144     bool selectloopDoReturn{false};
145     int  selectloopReturnValue{0};
146     int  placetostart{0};
147 
148     // Map of NetconP indexed by fd
149     map<int, NetconP> polldata;
150 #ifdef HAVE_KQUEUE
151     int kq{-1};
152 #endif
153     // The last time we did the periodic thing. Initialized by setperiodic()
154     struct timeval lasthdlcall;
155 
156     // The call back function and its parameter
157     int (*periodichandler)(void *){0};
158     void *periodicparam{0};
159     // The periodic interval
160     int periodicmillis{0};
161 
162     void periodictimeout(struct timeval *tv);
163     void periodictimeout(struct timespec *ts);
164     int maybecallperiodic();
165     int setselevents(int fd, int events);
166     int setselevents(NetconP& con, int events);
167 };
168 
SelectLoop()169 SelectLoop::SelectLoop()
170 {
171     m = new Internal;
172 }
173 
~SelectLoop()174 SelectLoop::~SelectLoop()
175 {
176     delete m;
177 }
178 
loopReturn(int value)179 void SelectLoop::loopReturn(int value)
180 {
181     m->selectloopDoReturn = true;
182     m->selectloopReturnValue = value;
183 }
184 
setperiodichandler(int (* handler)(void *),void * p,int ms)185 void SelectLoop::setperiodichandler(int (*handler)(void *), void *p, int ms)
186 {
187     m->periodichandler = handler;
188     m->periodicparam = p;
189     m->periodicmillis = ms;
190     if (m->periodicmillis > 0) {
191         gettimeofday(&m->lasthdlcall, 0);
192     }
193 }
194 
195 // Compute the appropriate timeout so that the select call returns in
196 // time to call the periodic routine.
periodictimeout(struct timeval * tv)197 void SelectLoop::Internal::periodictimeout(struct timeval *tv)
198 {
199     // If periodic not set, the select call times out and we loop
200     // after a very long time (we'd need to pass NULL to select for an
201     // infinite wait, and I'm too lazy to handle it)
202     if (periodicmillis <= 0) {
203         tv->tv_sec = 10000;
204         tv->tv_usec = 0;
205         return;
206     }
207 
208     struct timeval mtv;
209     gettimeofday(&mtv, 0);
210     int millis = periodicmillis - MILLIS(lasthdlcall, mtv);
211 
212     // millis <= 0 means we should have already done the thing. *dont* set the
213     // tv to 0, which means no timeout at all !
214     if (millis <= 0) {
215         millis = 1;
216     }
217     tv->tv_sec = millis / 1000;
218     tv->tv_usec = (millis % 1000) * 1000;
219 }
220 
periodictimeout(struct timespec * ts)221 void SelectLoop::Internal::periodictimeout(struct timespec *ts)
222 {
223     struct timeval tv;
224     periodictimeout(&tv);
225     ts->tv_sec = tv.tv_sec;
226     ts->tv_nsec = tv.tv_usec * 1000;
227 }
228 
229 
230 // Check if it's time to call the handler. selectloop will return to
231 // caller if either we or the handler return 0
maybecallperiodic()232 int SelectLoop::Internal::maybecallperiodic()
233 {
234     if (periodicmillis <= 0) {
235         return 1;
236     }
237 
238     struct timeval mtv;
239     gettimeofday(&mtv, 0);
240     int millis = periodicmillis - MILLIS(lasthdlcall, mtv);
241 
242     if (millis <= 0) {
243         lasthdlcall = mtv;
244         if (periodichandler) {
245             return periodichandler(periodicparam);
246         } else {
247             return 0;
248         }
249     }
250     return 1;
251 }
252 
253 #ifndef HAVE_KQUEUE
254 
doLoop()255 int SelectLoop::doLoop()
256 {
257     for (;;) {
258         if (m->selectloopDoReturn) {
259             m->selectloopDoReturn = false;
260             LOGDEB("Netcon::selectloop: returning on request\n");
261             return m->selectloopReturnValue;
262         }
263 
264         int nfds;
265         fd_set rd, wd;
266         FD_ZERO(&rd);
267         FD_ZERO(&wd);
268 
269         // Walk the netcon map and set up the read and write fd_sets
270         // for select()
271         nfds = 0;
272         for (auto& entry : m->polldata) {
273             NetconP& pll = entry.second;
274             int fd  = entry.first;
275             LOGDEB2("Selectloop: fd " << fd << " flags 0x"  <<
276                     pll->m_wantedEvents << "\n");
277             if (pll->m_wantedEvents & Netcon::NETCONPOLL_READ) {
278                 FD_SET(fd, &rd);
279                 nfds = std::max(nfds, fd + 1);
280             }
281             if (pll->m_wantedEvents & Netcon::NETCONPOLL_WRITE) {
282                 FD_SET(fd, &wd);
283                 nfds = std::max(nfds, fd + 1);
284             }
285         }
286 
287         if (nfds == 0) {
288             // This should never happen in a server as we should at least
289             // always monitor the main listening server socket. For a
290             // client, it's up to client code to avoid or process this
291             // condition.
292 
293             // Just in case there would still be open fds in there
294             // (with no r/w flags set). Should not be needed, but safer
295             m->polldata.clear();
296             LOGDEB1("Netcon::selectloop: no fds\n");
297             return 0;
298         }
299 
300         LOGDEB2("Netcon::selectloop: selecting, nfds = " << nfds << "\n");
301 
302         // Compute the next timeout according to what might need to be
303         // done apart from waiting for data
304         struct timeval tv;
305         m->periodictimeout(&tv);
306         // Wait for something to happen
307         int ret = select(nfds, &rd, &wd, 0, &tv);
308         LOGDEB2("Netcon::selectloop: nfds " << nfds <<
309                 " select returns " << ret << "\n");
310         if (ret < 0) {
311             LOGSYSERR("Netcon::selectloop", "select", "");
312             return -1;
313         }
314         if (m->periodicmillis > 0 && m->maybecallperiodic() <= 0) {
315             return 1;
316         }
317 
318         // Timeout, do it again.
319         if (ret == 0) {
320             continue;
321         }
322 
323         // Select returned > 0: at least one fd must be ready. Sweep the fd
324         // table and act on the ready ones.
325         // We don't start the fd sweep at 0, else some fds would be advantaged.
326         // Note that we do an fd sweep, not a map sweep. This is
327         // inefficient because the fd array may be very sparse. Otoh, the
328         // map may change between 2 sweeps, so that we'd have to be smart
329         // with the iterator. As the cost per unused fd is low (just 2 bit
330         // flag tests), we keep it like this for now
331         if (m->placetostart >= nfds) {
332             m->placetostart = 0;
333         }
334         int i, fd;
335         int activefds = 0;
336         for (i = 0, fd = m->placetostart; i < nfds; i++, fd++) {
337             if (fd >= nfds) {
338                 fd = 0;
339             }
340 
341             int canread = FD_ISSET(fd, &rd);
342             int canwrite = FD_ISSET(fd, &wd);
343             bool none = !canread && !canwrite;
344             LOGDEB2("Netcon::selectloop: fd " << fd << " "  <<
345                     (none ? "blocked" : "can") << " "  <<
346                     (canread ? "read" : "") << " "  <<
347                     (canwrite ? "write" : "") << "\n");
348             if (none) {
349                 continue;
350             }
351 
352             auto it = m->polldata.find(fd);
353             if (it == m->polldata.end()) {
354                 // This should never happen, because we only set our
355                 // own fds in the mask !
356                 LOGERR("Netcon::selectloop: fd "  << fd << " not found\n");
357                 continue;
358             }
359             activefds++;
360             // Next start will be one beyond last serviced (modulo nfds)
361             m->placetostart = fd + 1;
362 
363             NetconP& pll = it->second;
364             if (canread && pll->cando(Netcon::NETCONPOLL_READ) <= 0) {
365                 pll->m_wantedEvents &= ~Netcon::NETCONPOLL_READ;
366             }
367             if (canwrite && pll->cando(Netcon::NETCONPOLL_WRITE) <= 0) {
368                 pll->m_wantedEvents &= ~Netcon::NETCONPOLL_WRITE;
369             }
370             if (!(pll->m_wantedEvents &
371                   (Netcon::NETCONPOLL_WRITE | Netcon::NETCONPOLL_READ))) {
372                 LOGDEB0("Netcon::selectloop: fd " << it->first << " has 0x"
373                         << it->second->m_wantedEvents << " mask, erasing\n");
374                 m->polldata.erase(it);
375             }
376         } // fd sweep
377 
378         if (ret > 0 && activefds != ret) {
379             LOGERR("Select returned " << ret << " not equal to " <<
380                    activefds << " active fd found\n");
381             return -1;
382         }
383     } // forever loop
384     LOGERR("SelectLoop::doLoop: got out of loop !\n");
385     return -1;
386 }
387 
388 #else // -> Using kqueue: use select()
389 
doLoop()390 int SelectLoop::doLoop()
391 {
392     for (;;) {
393         if (m->selectloopDoReturn) {
394             m->selectloopDoReturn = false;
395             LOGDEB("Netcon::selectloop: returning on request\n");
396             return m->selectloopReturnValue;
397         }
398 
399         // Check that we do have something to wait for.
400         int nfds = 0;
401         for (auto& entry : m->polldata) {
402             NetconP& pll = entry.second;
403             if (pll->m_wantedEvents & Netcon::NETCONPOLL_READ) {
404                 nfds++;
405             } else if (pll->m_wantedEvents & Netcon::NETCONPOLL_WRITE) {
406                 nfds++;
407             }
408         }
409         if (nfds == 0) {
410             // This should never happen in a server as we should at least
411             // always monitor the main listening server socket. For a
412             // client, it's up to client code to avoid or process this
413             // condition.
414 
415             // Just in case there would still be open fds in there
416             // (with no r/w flags set). Should not be needed, but safer
417             m->polldata.clear();
418             LOGDEB1("Netcon::selectloop: no fds\n");
419             return 0;
420         }
421 
422         // Compute the next timeout according to what might need to be
423         // done apart from waiting for data
424         struct timespec ts;
425         m->periodictimeout(&ts);
426         // Wait for something to happen
427         vector<struct kevent> events;
428         events.resize(nfds);
429         LOGDEB1("Netcon::selectloop: kevent(), nfds = " << nfds << "\n");
430         int ret = kevent(m->kq, 0, 0, &events[0], events.size(), &ts);
431         LOGDEB1("Netcon::selectloop: nfds " << nfds <<
432                 " kevent returns " << ret << "\n");
433         if (ret < 0) {
434             LOGSYSERR("Netcon::selectloop", "kevent", "");
435             return -1;
436         }
437         if (m->periodicmillis > 0 && m->maybecallperiodic() <= 0) {
438             return 1;
439         }
440         if (ret == 0) {
441             // Timeout, do it again.
442             continue;
443         }
444 
445         for (int i = 0; i < ret; i++) {
446             struct kevent& ev = events[i];
447             if (ev.flags & EV_ERROR) {
448                 LOGSYSERR("Netcon::selectLoop", "kevent", "");
449                 LOGERR("Netcon::selectLoop: event error: " <<
450                        strerror(ev.data));
451                 return -1;
452             }
453             int canread = ev.filter == EVFILT_READ;
454             int canwrite = ev.filter == EVFILT_WRITE;
455             bool none = !canread && !canwrite;
456             LOGDEB1("Netcon::selectloop: fd " << int(ev.ident) << " "  <<
457                     (none ? "blocked" : "can") << " "  <<
458                     (canread ? "read" : "") << " "  <<
459                     (canwrite ? "write" : "") << "\n");
460             if (none) {
461                 LOGERR("Kevent returned unknown filter " << ev.filter <<endl);
462                 continue;
463             }
464 
465             auto it = m->polldata.find(int(ev.ident));
466             if (it == m->polldata.end()) {
467                 LOGERR("Netcon::selectloop: fd " << int(ev.ident) <<
468                        " not found\n");
469                 continue;
470             }
471             NetconP& pll = it->second;
472             if (canread && pll->cando(Netcon::NETCONPOLL_READ) <= 0) {
473                 pll->setselevents(pll->getselevents() &
474                                   ~Netcon::NETCONPOLL_READ);
475             }
476             if (canwrite && pll->cando(Netcon::NETCONPOLL_WRITE) <= 0) {
477                 pll->setselevents(pll->getselevents() &
478                                   ~Netcon::NETCONPOLL_WRITE);
479             }
480             if (!(pll->getselevents() &
481                   (Netcon::NETCONPOLL_WRITE | Netcon::NETCONPOLL_READ))) {
482                 LOGDEB0("Netcon::selectloop: fd " << it->first << " has 0x"
483                         << it->second->getselevents() << " mask, erasing\n");
484                 m->polldata.erase(it);
485             }
486         } // fd sweep
487 
488     } // forever loop
489     LOGERR("SelectLoop::doLoop: got out of loop !\n");
490     return -1;
491 }
492 
493 #endif // kqueue version
494 
setselevents(int fd,int events)495 int SelectLoop::Internal::setselevents(int fd, int events)
496 {
497 #ifdef HAVE_KQUEUE
498     auto it = polldata.find(fd);
499     if (it == polldata.end()) {
500         return -1;
501     }
502     return setselevents(it->second, events);
503 #endif
504     return 0;
505 }
506 
setselevents(NetconP & con,int events)507 int SelectLoop::Internal::setselevents(NetconP& con, int events)
508 {
509 #ifdef HAVE_KQUEUE
510     struct kevent event;
511     if (events & Netcon::NETCONPOLL_READ) {
512         EV_SET(&event, con->m_fd, EVFILT_READ, EV_ADD, 0, 0, 0);
513         if(kevent(kq, &event, 1, 0, 0, 0) < 0) {
514             LOGSYSERR("SelectLoop::addselcon", "kevent", "");
515             return -1;
516         }
517     } else {
518         EV_SET(&event, con->m_fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
519         kevent(kq, &event, 1, 0, 0, 0);
520     }
521     if (events & Netcon::NETCONPOLL_WRITE) {
522         EV_SET(&event, con->m_fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
523         if(kevent(kq, &event, 1, 0, 0, 0) < 0) {
524             LOGSYSERR("SelectLoop::addselcon", "kevent", "");
525             return -1;
526         }
527     } else {
528         EV_SET(&event, con->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
529         kevent(kq, &event, 1, 0, 0, 0);
530     }
531 #endif
532     return 0;
533 }
534 
535 // Add a connection to the monitored set. This can be used to change
536 // the event flags too (won't add duplicates)
addselcon(NetconP con,int events)537 int SelectLoop::addselcon(NetconP con, int events)
538 {
539     if (!con) {
540         return -1;
541     }
542     LOGDEB1("Netcon::addselcon: fd " << con->m_fd << "\n");
543     con->set_nonblock(1);
544     con->m_wantedEvents = events;
545     m->polldata[con->m_fd] = con;
546     con->setloop(this);
547     return m->setselevents(con, events);
548 }
549 
550 // Remove a connection from the monitored set.
remselcon(NetconP con)551 int SelectLoop::remselcon(NetconP con)
552 {
553     if (!con) {
554         return -1;
555     }
556     LOGDEB1("Netcon::remselcon: fd " << con->m_fd << "\n");
557     m->setselevents(con, 0);
558     auto it = m->polldata.find(con->m_fd);
559     if (it == m->polldata.end()) {
560         LOGDEB1("Netcon::remselcon: con not found for fd " <<
561                 con->m_fd << "\n");
562         return -1;
563     }
564     con->setloop(0);
565     m->polldata.erase(it);
566     return 0;
567 }
568 
569 //////////////////////////////////////////////////////////
570 // Base class (Netcon) methods
~Netcon()571 Netcon::~Netcon()
572 {
573     closeconn();
574     if (m_peer) {
575         free(m_peer);
576         m_peer = 0;
577     }
578 }
579 
closeconn()580 void Netcon::closeconn()
581 {
582     if (m_ownfd && m_fd >= 0) {
583         close(m_fd);
584     }
585     m_fd = -1;
586     m_ownfd = true;
587 }
588 
sterror()589 char *Netcon::sterror()
590 {
591     return strerror(errno);
592 }
593 
setpeer(const char * hostname)594 void Netcon::setpeer(const char *hostname)
595 {
596     if (m_peer) {
597         free(m_peer);
598     }
599     m_peer = strdup(hostname);
600 }
601 
settcpnodelay(int on)602 int Netcon::settcpnodelay(int on)
603 {
604     LOGDEB2("Netcon::settcpnodelay\n");
605     if (m_fd < 0) {
606         LOGERR("Netcon::settcpnodelay: connection not opened\n");
607         return -1;
608     }
609     char *cp = on ? (char *)&one : (char *)&zero;
610     if (setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, cp, sizeof(one)) < 0) {
611         LOGSYSERR("NetconCli::settcpnodelay", "setsockopt", "TCP_NODELAY");
612         return -1;
613     }
614     return 0;
615 }
616 
617 
618 // Set/reset non-blocking flag on fd
set_nonblock(int onoff)619 int Netcon::set_nonblock(int onoff)
620 {
621     int  flags = fcntl(m_fd, F_GETFL, 0);
622     if (flags != -1)   {
623         int newflags = onoff ? flags | O_NONBLOCK : flags & ~O_NONBLOCK;
624         if (newflags != flags)
625             if (fcntl(m_fd, F_SETFL, newflags) < 0) {
626                 return -1;
627             }
628     }
629     return flags;
630 }
631 
setselevents(int events)632 int Netcon::setselevents(int events)
633 {
634     m_wantedEvents = events;
635     if (m_loop) {
636         m_loop->m->setselevents(m_fd, events);
637     }
638     return m_wantedEvents;
639 }
640 
641 /////////////////////////////////////////////////////////////////////
642 // Data socket (NetconData) methods
643 
NetconData(bool cancellable)644 NetconData::NetconData(bool cancellable)
645     : m_buf(0), m_bufbase(0), m_bufbytes(0), m_bufsize(0), m_wkfds{-1,-1}
646 {
647     if (cancellable) {
648         if (pipe(m_wkfds) < 0) {
649             LOGSYSERR("NetconData::NetconData", "pipe", "");
650             m_wkfds[0] = m_wkfds[1] = -1;
651         }
652         LOGDEB2("NetconData:: m_wkfds[0] " << m_wkfds[0] << " m_wkfds[1] " <<
653                m_wkfds[1] << endl);
654         for (int i = 0; i < 2; i++) {
655             int flags = fcntl(m_wkfds[i], F_GETFL, 0);
656             fcntl(m_wkfds[i], F_SETFL, flags | O_NONBLOCK);
657         }
658     }
659 }
660 
~NetconData()661 NetconData::~NetconData()
662 {
663     freeZ(m_buf);
664     m_bufbase = 0;
665     m_bufbytes = m_bufsize = 0;
666     for (int i = 0; i < 2; i++) {
667         if (m_wkfds[i] >= 0) {
668             close(m_wkfds[i]);
669         }
670     }
671 }
672 
send(const char * buf,int cnt,int expedited)673 int NetconData::send(const char *buf, int cnt, int expedited)
674 {
675     LOGDEB2("NetconData::send: fd " << m_fd << " cnt " << cnt <<
676             " expe " << expedited << "\n");
677     int flag = 0;
678     if (m_fd < 0) {
679         LOGERR("NetconData::send: connection not opened\n");
680         return -1;
681     }
682     if (expedited) {
683         LOGDEB2("NetconData::send: expedited data, count " <<cnt << " bytes\n");
684         flag = MSG_OOB;
685     }
686     int ret;
687     // There is a bug in the uthread version of sendto() in FreeBSD at
688     // least up to 2.2.7, so avoid using it when possible
689     if (flag) {
690         ret = ::send(m_fd, buf, cnt, flag);
691     } else {
692         ret = ::write(m_fd, buf, cnt);
693     }
694 
695     // Note: byte count may be different from cnt if fd is non-blocking
696     if (ret < 0) {
697         char fdcbuf[20];
698         sprintf(fdcbuf, "%d", m_fd);
699         LOGSYSERR("NetconData::send", "send", fdcbuf);
700     }
701     return ret;
702 }
703 
cancelReceive()704 void NetconData::cancelReceive()
705 {
706     if (m_wkfds[1] >= 0) {
707         LOGDEB2("NetconData::cancelReceive: writing to " << m_wkfds[1] << endl);
708         // We can't do a thing about the ::write return value, the
709         // following nonsense is for cancelling warnings
710         int ret = ::write(m_wkfds[1], "!", 1);
711         PRETEND_USE(ret);
712     }
713 }
714 
715 // Receive at most cnt bytes (maybe less)
receive(char * buf,int cnt,int timeo)716 int NetconData::receive(char *buf, int cnt, int timeo)
717 {
718     LOGDEB2("NetconData::receive: cnt " << cnt << " timeo "  << timeo <<
719             " m_buf 0x" << m_buf << " m_bufbytes " << m_bufbytes << "\n");
720 
721     if (m_fd < 0) {
722         LOGERR("NetconData::receive: connection not opened\n");
723         return -1;
724     }
725 
726     int fromibuf = 0;
727     // Get whatever might have been left in the buffer by a previous
728     // getline, except if we're called to fill the buffer of course
729     if (m_buf && m_bufbytes > 0 && (buf < m_buf || buf > m_buf + m_bufsize)) {
730         fromibuf = std::min(m_bufbytes, cnt);
731         memcpy(buf, m_bufbase, fromibuf);
732         m_bufbytes -= fromibuf;
733         m_bufbase += fromibuf;
734         cnt -= fromibuf;
735         LOGDEB2("NetconData::receive: got " << fromibuf << " from mbuf\n");
736         if (cnt <= 0) {
737             return fromibuf;
738         }
739     }
740 
741     if (timeo > 0) {
742         struct timeval tv;
743         tv.tv_sec = timeo;
744         tv.tv_usec =  0;
745         fd_set rd;
746         FD_ZERO(&rd);
747         FD_SET(m_fd, &rd);
748         bool cancellable = (m_wkfds[0] >= 0);
749         if (cancellable) {
750             LOGDEB2("NetconData::receive: cancel fd " << m_wkfds[0] << endl);
751             FD_SET(m_wkfds[0], &rd);
752         }
753         int nfds = std::max(m_fd, m_wkfds[0]) + 1;
754 
755         int ret = select(nfds, &rd, 0, 0, &tv);
756         LOGDEB2("NetconData::receive: select returned " << ret << endl);
757 
758         if (cancellable && FD_ISSET(m_wkfds[0], &rd)) {
759             char b[100];
760             // We can't do a thing about the return value, the
761             // following nonsense is for cancelling warnings
762             int ret = ::read(m_wkfds[0], b, 100);
763             PRETEND_USE(ret);
764             return Cancelled;
765         }
766 
767         if (!FD_ISSET(m_fd, &rd)) {
768             m_didtimo = 1;
769             return TimeoutOrError;
770         }
771 
772         if (ret < 0) {
773             LOGSYSERR("NetconData::receive", "select", "");
774             m_didtimo = 0;
775             return TimeoutOrError;
776         }
777     }
778 
779     m_didtimo = 0;
780     if ((cnt = read(m_fd, buf + fromibuf, cnt)) < 0) {
781         LOGSYSERR("NetconData::receive", "read", m_fd);
782         return -1;
783     }
784     LOGDEB2("NetconData::receive: normal return, fromibuf " << fromibuf <<
785             " cnt "  << cnt << "\n");
786     return fromibuf + cnt;
787 }
788 
789 // Receive exactly cnt bytes (except for timeout)
doreceive(char * buf,int cnt,int timeo)790 int NetconData::doreceive(char *buf, int cnt, int timeo)
791 {
792     int got, cur;
793     LOGDEB2("Netcon::doreceive: cnt " << cnt << ", timeo " << timeo << "\n");
794     cur = 0;
795     while (cnt > cur) {
796         got = receive(buf, cnt - cur, timeo);
797         LOGDEB2("Netcon::doreceive: got " << got << "\n");
798         if (got < 0) {
799             return got;
800         }
801         if (got == 0) {
802             return cur;
803         }
804         cur += got;
805         buf += got;
806     }
807     return cur;
808 }
809 
810 // Read data until cnt-1 characters are read or a newline is found. Add
811 // null char at end of buffer and return.
812 // As we don't know where the newline will be and it would be inefficient to
813 // read a character at a time, we use a buffer
814 // Unlike fgets, we return an integer status:
815 // >0: number of characters returned, not including the final 0
816 //  0: EOF reached, no chars transferred
817 // -1: error
818 static const int defbufsize = 200;
getline(char * buf,int cnt,int timeo)819 int NetconData::getline(char *buf, int cnt, int timeo)
820 {
821     LOGDEB2("NetconData::getline: cnt " << cnt << ", timeo " <<
822             timeo << "\n");
823     if (m_buf == 0) {
824         if ((m_buf = (char *)malloc(defbufsize)) == 0) {
825             LOGSYSERR("NetconData::getline: Out of mem", "malloc", "");
826             return -1;
827         }
828         m_bufsize = defbufsize;
829         m_bufbase = m_buf;
830         m_bufbytes = 0;
831     }
832 
833     char *cp = buf;
834     for (;;) {
835         // Transfer from buffer. Have to take a lot of care to keep counts and
836         // pointers consistant in all end cases
837         int maxtransf = std::min(m_bufbytes, cnt - 1);
838         int nn = maxtransf;
839         LOGDEB2("Before loop, bufbytes " << m_bufbytes << ", maxtransf " <<
840                 maxtransf << ", nn: " << nn << "\n");
841         for (nn = maxtransf; nn > 0;) {
842             // This is not pretty but we want nn to be decremented for
843             // each byte copied (even newline), and not become -1 if
844             // we go to the end. Better ways welcome!
845             nn--;
846             if ((*cp++ = *m_bufbase++) == '\n') {
847                 break;
848             }
849         }
850         // Update counts
851         maxtransf -= nn; // Actual count transferred
852         m_bufbytes -= maxtransf;
853         cnt -= maxtransf;
854         LOGDEB2("After transfer: actual transf " << maxtransf << " cnt " <<
855                 cnt << ", m_bufbytes " << m_bufbytes << "\n");
856 
857         // Finished ?
858         if (cnt <= 1 || (cp > buf && cp[-1] == '\n')) {
859             *cp = 0;
860             return cp - buf;
861         }
862 
863         // Transfer from net
864         m_bufbase = m_buf;
865         m_bufbytes = receive(m_buf, m_bufsize, timeo);
866         if (m_bufbytes == 0) {
867             // EOF
868             *cp = 0;
869             return cp - buf;
870         }
871         if (m_bufbytes < 0) {
872             m_bufbytes = 0;
873             *cp = 0;
874             return -1;
875         }
876     }
877 }
878 
879 // Called when selectloop detects that data can be read or written on
880 // the connection. The user callback would normally have been set
881 // up. If it is, call it and return. Else, perform housecleaning: read
882 // and discard.
cando(Netcon::Event reason)883 int NetconData::cando(Netcon::Event reason)
884 {
885     LOGDEB2("NetconData::cando\n");
886     if (m_user) {
887         return m_user->data(this, reason);
888     }
889 
890     // No user callback. Clean up by ourselves
891     if (reason & NETCONPOLL_READ) {
892 #define BS 200
893         char buf[BS];
894         int n;
895         if ((n = receive(buf, BS)) < 0) {
896             LOGSYSERR("NetconData::cando", "receive", "");
897             return -1;
898         }
899         if (n == 0) {
900             // EOF
901             return 0;
902         }
903     }
904     m_wantedEvents &= ~NETCONPOLL_WRITE;
905     return 1;
906 }
907 
908 ///////////////////////////////////////////////////////////////////////
909 // Methods for a client connection (NetconCli)
openconn(const char * host,unsigned int port,int timeo)910 int NetconCli::openconn(const char *host, unsigned int port, int timeo)
911 {
912     int ret = -1;
913     LOGDEB2("Netconcli::openconn: host " << host << ", port "  << port << "\n");
914 
915     closeconn();
916 
917     struct sockaddr *saddr;
918     socklen_t addrsize;
919 
920     struct sockaddr_in ip_addr;
921     struct sockaddr_un unix_addr;
922     if (host[0] != '/') {
923         memset(&ip_addr, 0, sizeof(ip_addr));
924         ip_addr.sin_family = AF_INET;
925         ip_addr.sin_port = htons(port);
926 
927         // Server name may be host name or IP address
928         int addr;
929         if ((addr = inet_addr(host)) != -1) {
930             memcpy(&ip_addr.sin_addr, &addr, sizeof(addr));
931         } else {
932             struct hostent *hp;
933             if ((hp = gethostbyname(host)) == 0) {
934                 LOGERR("NetconCli::openconn: gethostbyname(" << host <<
935                        ") failed\n");
936                 return -1;
937             }
938             memcpy(&ip_addr.sin_addr, hp->h_addr, hp->h_length);
939         }
940 
941         if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
942             LOGSYSERR("NetconCli::openconn", "socket", "");
943             return -1;
944         }
945         addrsize = sizeof(ip_addr);
946         saddr = (sockaddr*)&ip_addr;
947     } else {
948         memset(&unix_addr, 0, sizeof(unix_addr));
949         unix_addr.sun_family = AF_UNIX;
950         if (strlen(host) > UNIX_PATH_MAX - 1) {
951             LOGERR("NetconCli::openconn: name too long: " << host << "\n");
952             return -1;
953         }
954         strcpy(unix_addr.sun_path, host);
955 
956         if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
957             LOGSYSERR("NetconCli::openconn", "socket", "");
958             return -1;
959         }
960         addrsize = sizeof(unix_addr);
961         saddr = (sockaddr*)&unix_addr;
962     }
963     if (timeo > 0) {
964         set_nonblock(1);
965     }
966 
967     if (connect(m_fd, saddr, addrsize) < 0) {
968         if (timeo > 0) {
969             if (errno != EINPROGRESS) {
970                 goto out;
971             }
972             if (select1(m_fd, timeo, 1) == 1) {
973                 goto connectok;
974             }
975         }
976         if (m_silentconnectfailure == 0) {
977             LOGSYSERR("NetconCli", "connect", "");
978         }
979         goto out;
980     }
981 connectok:
982     if (timeo > 0) {
983         set_nonblock(0);
984     }
985 
986     LOGDEB2("NetconCli::connect: setting keepalive\n");
987     if (setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE,
988                    (char *)&one, sizeof(one)) < 0) {
989         LOGSYSERR("NetconCli::connect", "setsockopt", "KEEPALIVE");
990     }
991     setpeer(host);
992     LOGDEB2("NetconCli::openconn: connection opened ok\n");
993     ret = 0;
994 out:
995     if (ret < 0) {
996         closeconn();
997     }
998     return ret;
999 }
1000 
1001 // Same as previous, but get the port number from services
openconn(const char * host,const char * serv,int timeo)1002 int NetconCli::openconn(const char *host, const char *serv, int timeo)
1003 {
1004     LOGDEB2("Netconcli::openconn: host " << host << ", serv " << serv << "\n");
1005 
1006     if (host[0]  != '/') {
1007         struct servent *sp;
1008         if ((sp = getservbyname(serv, "tcp")) == 0) {
1009             LOGERR("NetconCli::openconn: getservbyname failed for " << serv
1010                    << "\n");
1011             return -1;
1012         }
1013         // Callee expects the port number in host byte order
1014         return openconn(host, ntohs(sp->s_port), timeo);
1015     } else {
1016         return openconn(host, (unsigned int)0, timeo);
1017     }
1018 }
1019 
1020 
setconn(int fd)1021 int NetconCli::setconn(int fd)
1022 {
1023     LOGDEB2("Netconcli::setconn: fd " << fd << "\n");
1024     closeconn();
1025 
1026     m_fd = fd;
1027     m_ownfd = false;
1028     setpeer("");
1029 
1030     return 0;
1031 }
1032 
1033 ///////////////////////////////////////////////////////////////////////
1034 // Methods for the main (listening) server connection
1035 
~NetconServLis()1036 NetconServLis::~NetconServLis()
1037 {
1038 #ifdef NETCON_ACCESSCONTROL
1039     freeZ(okaddrs.intarray);
1040     freeZ(okmasks.intarray);
1041 #endif
1042 }
1043 
1044 #if 0
1045 // code for dumping a struct servent
1046 static void dump_servent(struct servent *servp)
1047 {
1048     fprintf(stderr, "Official name %s\n", servp->s_name);
1049     for (char **cpp = servp->s_aliases; *cpp; cpp++) {
1050         fprintf(stderr, "Nickname %s\n", *cpp);
1051     }
1052     fprintf(stderr, "Port %d\n", (int)ntohs((short)servp->s_port));
1053     fprintf(stderr, "Proto %s\n", servp->s_proto);
1054 }
1055 #endif
1056 
1057 // Set up service.
openservice(const char * serv,int backlog)1058 int NetconServLis::openservice(const char *serv, int backlog)
1059 {
1060     int port;
1061     struct servent  *servp;
1062     if (!serv) {
1063         LOGERR("NetconServLis::openservice: null serv??\n");
1064         return -1;
1065     }
1066     LOGDEB1("NetconServLis::openservice: serv " << serv << "\n");
1067 #ifdef NETCON_ACCESSCONTROL
1068     if (initperms(serv) < 0) {
1069         return -1;
1070     }
1071 #endif
1072 
1073     m_serv = serv;
1074     if (serv[0] != '/') {
1075         if ((servp = getservbyname(serv, "tcp")) == 0) {
1076             LOGERR("NetconServLis::openservice: getservbyname failed for " <<
1077                    serv << "\n");
1078             return -1;
1079         }
1080         port = (int)ntohs((short)servp->s_port);
1081         return openservice(port, backlog);
1082     } else {
1083         if (strlen(serv) > UNIX_PATH_MAX - 1) {
1084             LOGERR("NetconServLis::openservice: too long for AF_UNIX: " <<
1085                    serv << "\n");
1086             return -1;
1087         }
1088         int ret = -1;
1089         struct sockaddr_un  addr;
1090         if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
1091             LOGSYSERR("NetconServLis", "socket", "");
1092             return -1;
1093         }
1094         memset(&addr, 0, sizeof(addr));
1095         addr.sun_family = AF_UNIX;
1096         strcpy(addr.sun_path, serv);
1097 
1098         if (::bind(m_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1099             LOGSYSERR("NetconServLis", "bind", "");
1100             goto out;
1101         }
1102         if (listen(m_fd, backlog) < 0) {
1103             LOGSYSERR("NetconServLis", "listen", "");
1104             goto out;
1105         }
1106 
1107         LOGDEB1("NetconServLis::openservice: service opened ok\n");
1108         ret = 0;
1109 out:
1110         if (ret < 0 && m_fd >= 0) {
1111             close(m_fd);
1112             m_fd = -1;
1113         }
1114         return ret;
1115     }
1116 }
1117 
1118 // Port is a natural host integer value
openservice(int port,int backlog)1119 int NetconServLis::openservice(int port, int backlog)
1120 {
1121     LOGDEB1("NetconServLis::openservice: port " << port << "\n");
1122 #ifdef NETCON_ACCESSCONTROL
1123     if (initperms(port) < 0) {
1124         return -1;
1125     }
1126 #endif
1127     int ret = -1;
1128     struct sockaddr_in  ipaddr;
1129     if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
1130         LOGSYSERR("NetconServLis", "socket", "");
1131         return -1;
1132     }
1133     (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
1134 #ifdef SO_REUSEPORT
1135     (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT, (char *)&one, sizeof(one));
1136 #endif /*SO_REUSEPORT*/
1137     memset(&ipaddr, 0, sizeof(ipaddr));
1138     ipaddr.sin_family = AF_INET;
1139     ipaddr.sin_addr.s_addr = htonl(INADDR_ANY);
1140     ipaddr.sin_port = htons((short)port);
1141     if (::bind(m_fd, (struct sockaddr *)&ipaddr, sizeof(ipaddr)) < 0) {
1142         LOGSYSERR("NetconServLis", "bind", "");
1143         goto out;
1144     }
1145     if (listen(m_fd, backlog) < 0) {
1146         LOGSYSERR("NetconServLis", "listen", "");
1147         goto out;
1148     }
1149 
1150     LOGDEB1("NetconServLis::openservice: service opened ok\n");
1151     ret = 0;
1152 out:
1153     if (ret < 0 && m_fd >= 0) {
1154         close(m_fd);
1155         m_fd = -1;
1156     }
1157     return ret;
1158 }
1159 
1160 #ifdef NETCON_ACCESSCONTROL
initperms(int port)1161 int NetconServLis::initperms(int port)
1162 {
1163     if (permsinit) {
1164         return 0;
1165     }
1166 
1167     char sport[30];
1168     sprintf(sport, "%d", port);
1169     return initperms(sport);
1170 }
1171 
1172 // Get authorized address lists from parameter file. This is disabled for now
initperms(const char * serv)1173 int NetconServLis::initperms(const char *serv)
1174 {
1175     if (permsinit) {
1176         return 0;
1177     }
1178 
1179     if (serv == 0 || *serv == 0 || strlen(serv) > 80) {
1180         LOGERR("NetconServLis::initperms: bad service name " << serv << "\n");
1181         return -1;
1182     }
1183 
1184     char keyname[100];
1185     sprintf(keyname, "%s_okaddrs", serv);
1186     if (genparams->getparam(keyname, &okaddrs, 1) < 0) {
1187         serv = "default";
1188         sprintf(keyname, "%s_okaddrs", serv);
1189         if (genparams->getparam(keyname, &okaddrs) < 0) {
1190             LOGERR("NetconServLis::initperms: no okaddrs found in config file\n");
1191             return -1;
1192         }
1193     }
1194     sprintf(keyname, "%s_okmasks", serv);
1195     if (genparams->getparam(keyname, &okmasks)) {
1196         LOGERR("NetconServLis::initperms: okmasks not found\n");
1197         return -1;
1198     }
1199     if (okaddrs.len == 0 || okmasks.len == 0) {
1200         LOGERR("NetconServLis::initperms: len 0 for okmasks or okaddrs\n");
1201         return -1;
1202     }
1203 
1204     permsinit = 1;
1205     return 0;
1206 }
1207 #endif /* NETCON_ACCESSCONTROL */
1208 
1209 // Sample cando routine for server master connection: delete newly
1210 // accepted connection. What else ?
1211 // This is to be overriden by a derived class method for an application
1212 // using the selectloop thing
cando(Netcon::Event reason)1213 int  NetconServLis::cando(Netcon::Event reason)
1214 {
1215     delete accept();
1216     return 1;
1217 }
1218 
1219 NetconServCon *
accept(int timeo)1220 NetconServLis::accept(int timeo)
1221 {
1222     LOGDEB("NetconServLis::accept\n");
1223 
1224     if (timeo > 0) {
1225         int ret = select1(m_fd, timeo);
1226         if (ret == 0) {
1227             LOGDEB2("NetconServLis::accept timed out\n");
1228             m_didtimo = 1;
1229             return 0;
1230         }
1231         if (ret < 0) {
1232             LOGSYSERR("NetconServLis::accept", "select", "");
1233             return 0;
1234         }
1235     }
1236     m_didtimo = 0;
1237 
1238     NetconServCon *con = 0;
1239     int newfd = -1;
1240     struct sockaddr_in who;
1241     struct sockaddr_un uwho;
1242     if (m_serv.empty() || m_serv[0] != '/') {
1243         SOCKLEN_T clilen = (SOCKLEN_T)sizeof(who);
1244         if ((newfd = ::accept(m_fd, (struct sockaddr *)&who, &clilen)) < 0) {
1245             LOGSYSERR("NetconServCon::accept", "accept", "");
1246             goto out;
1247         }
1248 #ifdef NETCON_ACCESSCONTROL
1249         if (checkperms(&who, clilen) < 0) {
1250             goto out;
1251         }
1252 #endif
1253     } else {
1254         SOCKLEN_T clilen = (SOCKLEN_T)sizeof(uwho);
1255         if ((newfd = ::accept(m_fd, (struct sockaddr *)&uwho, &clilen)) < 0) {
1256             LOGSYSERR("NetconServCon::accept", "accept", "");
1257             goto out;
1258         }
1259     }
1260 
1261     con = new NetconServCon(newfd);
1262     if (con == 0) {
1263         LOGERR("NetconServLis::accept: new NetconServCon failed\n");
1264         goto out;
1265     }
1266 
1267     // Retrieve peer's host name. Errors are non fatal
1268     if (m_serv.empty() || m_serv[0] != '/') {
1269         struct hostent *hp;
1270         if ((hp = gethostbyaddr((char *) & (who.sin_addr),
1271                                 sizeof(struct in_addr), AF_INET)) == 0) {
1272             LOGERR("NetconServLis::accept: gethostbyaddr failed for addr 0x" <<
1273                    who.sin_addr.s_addr << "\n");
1274             con->setpeer(inet_ntoa(who.sin_addr));
1275         } else {
1276             con->setpeer(hp->h_name);
1277         }
1278     } else {
1279         con->setpeer(m_serv.c_str());
1280     }
1281 
1282     LOGDEB2("NetconServLis::accept: setting keepalive\n");
1283     if (setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE,
1284                    (char *)&one, sizeof(one)) < 0) {
1285         LOGSYSERR("NetconServLis::accept", "setsockopt", "KEEPALIVE");
1286     }
1287     LOGDEB2("NetconServLis::accept: got connect from " << con->getpeer() <<
1288             "\n");
1289 
1290 out:
1291     if (con == 0 && newfd >= 0) {
1292         close(newfd);
1293     }
1294     return con;
1295 }
1296 
1297 #ifdef NETCON_ACCESSCONTROL
1298 int
checkperms(void * cl,int)1299 NetconServLis::checkperms(void *cl, int)
1300 {
1301     // If okmasks and addrs were not initialized, the default is allow to all
1302     if (okmasks.len <= 0 || okaddrs.len <= 0) {
1303         return 0;
1304     }
1305 
1306     struct sockaddr *addr = (struct sockaddr *)cl;
1307     unsigned long ip_addr;
1308 
1309     if (addr->sa_family != AF_INET) {
1310         LOGERR("NetconServLis::checkperms: connection from non-INET addr !\n");
1311         return -1;
1312     }
1313 
1314     ip_addr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr);
1315     LOGDEB2("checkperms: ip_addr: 0x" << ip_addr << "\n");
1316     for (int i = 0; i < okaddrs.len; i++) {
1317         unsigned int mask;
1318         if (i < okmasks.len) {
1319             mask = okmasks.intarray[i];
1320         } else {
1321             mask = okmasks.intarray[okmasks.len - 1];
1322         }
1323         LOGDEB2("checkperms: trying okaddr 0x" << okaddrs.intarray[i] <<
1324                 ", mask 0x" << mask << "\n");
1325         if ((ip_addr & mask) == (okaddrs.intarray[i] & mask)) {
1326             return (0);
1327         }
1328     }
1329     LOGERR("NetconServLis::checkperm: connection from bad address 0x" <<
1330            ip_addr << "\n");
1331     return -1;
1332 }
1333 #endif /* NETCON_ACCESSCONTROL */
1334