]> git.stg.codes - stg.git/blob - stargazer/plugins/other/radius/radius.cpp
Lower C++ version, minmal GCC version, compiler-related flags.
[stg.git] / stargazer / plugins / other / radius / radius.cpp
1 /*
2  *    This program is free software; you can redistribute it and/or modify
3  *    it under the terms of the GNU General Public License as published by
4  *    the Free Software Foundation; either version 2 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 General Public License for more details.
11  *
12  *    You should have received a copy of the GNU General Public License
13  *    along with this program; if not, write to the Free Software
14  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
15  */
16
17 /*
18  *    Author : Maxim Mamontov <faust@stargazer.dp.ua>
19  */
20
21 #include "radius.h"
22
23 #include "stg/store.h"
24 #include "stg/users.h"
25 #include "stg/plugin_creator.h"
26 #include "stg/common.h"
27
28 #include <algorithm>
29 #include <stdexcept>
30 #include <csignal>
31 #include <cerrno>
32 #include <cstring>
33
34 #include <sys/types.h>
35 #include <sys/socket.h>
36 #include <sys/un.h> // UNIX
37 #include <netinet/in.h> // IP
38 #include <netinet/tcp.h> // TCP
39 #include <netdb.h>
40
41 using STG::Config;
42 using STG::Conn;
43
44 namespace
45 {
46
47 PLUGIN_CREATOR<RADIUS> creator;
48
49 }
50
51 extern "C" PLUGIN * GetPlugin()
52 {
53     return creator.GetPlugin();
54 }
55
56 RADIUS::RADIUS()
57     : m_config(),
58       m_running(false),
59       m_stopped(true),
60       m_users(NULL),
61       m_store(NULL),
62       m_listenSocket(0),
63       m_logger(GetPluginLogger(GetStgLogger(), "radius"))
64 {
65 }
66
67 int RADIUS::ParseSettings()
68 {
69     try {
70         m_config = STG::Config(m_settings);
71         return reconnect() ? 0 : -1;
72     } catch (const std::runtime_error& ex) {
73         m_logger("Failed to parse settings. %s", ex.what());
74         return -1;
75     }
76 }
77
78 int RADIUS::Start()
79 {
80     if (m_running)
81         return 0;
82
83     int res = pthread_create(&m_thread, NULL, run, this);
84     if (res == 0)
85         return 0;
86
87     m_error = strerror(res);
88     m_logger("Failed to create thread: '" + m_error + "'.");
89     return -1;
90 }
91
92 int RADIUS::Stop()
93 {
94     std::set<std::string>::const_iterator it = m_logins.begin();
95     for (; it != m_logins.end(); ++it)
96         m_users->Unauthorize(*it, this, "Stopping RADIUS plugin.");
97     m_logins.clear();
98
99     if (m_stopped)
100         return 0;
101
102     m_running = false;
103
104     for (size_t i = 0; i < 25 && !m_stopped; i++) {
105         struct timespec ts = {0, 200000000};
106         nanosleep(&ts, NULL);
107     }
108
109     if (m_stopped) {
110         pthread_join(m_thread, NULL);
111         return 0;
112     }
113
114     if (m_config.connectionType == Config::UNIX)
115         unlink(m_config.bindAddress.c_str());
116
117     m_error = "Failed to stop thread.";
118     m_logger(m_error);
119     return -1;
120 }
121 //-----------------------------------------------------------------------------
122 void* RADIUS::run(void* d)
123 {
124     sigset_t signalSet;
125     sigfillset(&signalSet);
126     pthread_sigmask(SIG_BLOCK, &signalSet, NULL);
127
128     static_cast<RADIUS *>(d)->runImpl();
129
130     return NULL;
131 }
132
133 bool RADIUS::reconnect()
134 {
135     if (!m_conns.empty())
136     {
137         std::deque<STG::Conn *>::const_iterator it;
138         for (it = m_conns.begin(); it != m_conns.end(); ++it)
139             delete(*it);
140         m_conns.clear();
141     }
142     if (m_listenSocket != 0)
143     {
144         shutdown(m_listenSocket, SHUT_RDWR);
145         close(m_listenSocket);
146     }
147     if (m_config.connectionType == Config::UNIX)
148         m_listenSocket = createUNIX();
149     else
150         m_listenSocket = createTCP();
151     if (m_listenSocket == 0)
152         return false;
153     if (listen(m_listenSocket, 100) == -1)
154     {
155         m_error = std::string("Error starting to listen socket: ") + strerror(errno);
156         m_logger(m_error);
157         return false;
158     }
159     return true;
160 }
161
162 int RADIUS::createUNIX() const
163 {
164     int fd = socket(AF_UNIX, SOCK_STREAM, 0);
165     if (fd == -1)
166     {
167         m_error = std::string("Error creating UNIX socket: ") + strerror(errno);
168         m_logger(m_error);
169         return 0;
170     }
171     struct sockaddr_un addr;
172     memset(&addr, 0, sizeof(addr));
173     addr.sun_family = AF_UNIX;
174     strncpy(addr.sun_path, m_config.bindAddress.c_str(), m_config.bindAddress.length());
175     unlink(m_config.bindAddress.c_str());
176     if (bind(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
177     {
178         shutdown(fd, SHUT_RDWR);
179         close(fd);
180         m_error = std::string("Error binding UNIX socket: ") + strerror(errno);
181         m_logger(m_error);
182         return 0;
183     }
184     chown(m_config.bindAddress.c_str(), m_config.sockUID, m_config.sockGID);
185     if (m_config.sockMode != static_cast<mode_t>(-1))
186         chmod(m_config.bindAddress.c_str(), m_config.sockMode);
187     return fd;
188 }
189
190 int RADIUS::createTCP() const
191 {
192     addrinfo hints;
193     memset(&hints, 0, sizeof(addrinfo));
194
195     hints.ai_family = AF_INET;       /* Allow IPv4 */
196     hints.ai_socktype = SOCK_STREAM; /* Stream socket */
197     hints.ai_flags = AI_PASSIVE;     /* For wildcard IP address */
198     hints.ai_protocol = 0;           /* Any protocol */
199     hints.ai_canonname = NULL;
200     hints.ai_addr = NULL;
201     hints.ai_next = NULL;
202
203     addrinfo* ais = NULL;
204     int res = getaddrinfo(m_config.bindAddress.c_str(), m_config.portStr.c_str(), &hints, &ais);
205     if (res != 0)
206     {
207         m_error = "Error resolving address '" + m_config.bindAddress + "': " + gai_strerror(res);
208         m_logger(m_error);
209         return 0;
210     }
211
212     for (addrinfo* ai = ais; ai != NULL; ai = ai->ai_next)
213     {
214         int fd = socket(AF_INET, SOCK_STREAM, 0);
215         if (fd == -1)
216         {
217             m_error = std::string("Error creating TCP socket: ") + strerror(errno);
218             m_logger(m_error);
219             freeaddrinfo(ais);
220             return 0;
221         }
222         if (bind(fd, ai->ai_addr, ai->ai_addrlen) == -1)
223         {
224             shutdown(fd, SHUT_RDWR);
225             close(fd);
226             m_error = std::string("Error binding TCP socket: ") + strerror(errno);
227             m_logger(m_error);
228             continue;
229         }
230         freeaddrinfo(ais);
231         return fd;
232     }
233
234     m_error = "Failed to resolve '" + m_config.bindAddress;
235     m_logger(m_error);
236
237     freeaddrinfo(ais);
238     return 0;
239 }
240
241 void RADIUS::runImpl()
242 {
243     m_running = true;
244     m_stopped = false;
245
246     while (m_running) {
247         fd_set fds;
248
249         buildFDSet(fds);
250
251         struct timeval tv;
252         tv.tv_sec = 0;
253         tv.tv_usec = 500000;
254
255         int res = select(maxFD() + 1, &fds, NULL, NULL, &tv);
256         if (res < 0)
257         {
258             if (errno == EINTR)
259                 continue;
260             m_error = std::string("'select' is failed: '") + strerror(errno) + "'.";
261             m_logger(m_error);
262             break;
263         }
264
265         if (!m_running)
266             break;
267
268         if (res > 0)
269             handleEvents(fds);
270         else
271         {
272             for (std::deque<Conn*>::iterator it = m_conns.begin(); it != m_conns.end(); ++it)
273                 (*it)->tick();
274         }
275
276         cleanupConns();
277     }
278
279     m_stopped = true;
280 }
281
282 int RADIUS::maxFD() const
283 {
284     int maxFD = m_listenSocket;
285     std::deque<STG::Conn *>::const_iterator it;
286     for (it = m_conns.begin(); it != m_conns.end(); ++it)
287         if (maxFD < (*it)->sock())
288             maxFD = (*it)->sock();
289     return maxFD;
290 }
291
292 void RADIUS::buildFDSet(fd_set & fds) const
293 {
294     FD_ZERO(&fds);
295     FD_SET(m_listenSocket, &fds);
296     std::deque<STG::Conn *>::const_iterator it;
297     for (it = m_conns.begin(); it != m_conns.end(); ++it)
298         FD_SET((*it)->sock(), &fds);
299 }
300
301 void RADIUS::cleanupConns()
302 {
303     std::deque<STG::Conn *>::iterator pos;
304     for (pos = m_conns.begin(); pos != m_conns.end(); ++pos)
305         if (!(*pos)->isOk()) {
306             delete *pos;
307             *pos = NULL;
308         }
309
310     pos = std::remove(m_conns.begin(), m_conns.end(), static_cast<STG::Conn *>(NULL));
311     m_conns.erase(pos, m_conns.end());
312 }
313
314 void RADIUS::handleEvents(const fd_set & fds)
315 {
316     if (FD_ISSET(m_listenSocket, &fds))
317         acceptConnection();
318     else
319     {
320         std::deque<STG::Conn *>::iterator it;
321         for (it = m_conns.begin(); it != m_conns.end(); ++it)
322             if (FD_ISSET((*it)->sock(), &fds))
323                 (*it)->read();
324             else
325                 (*it)->tick();
326     }
327 }
328
329 void RADIUS::acceptConnection()
330 {
331     if (m_config.connectionType == Config::UNIX)
332         acceptUNIX();
333     else
334         acceptTCP();
335 }
336
337 void RADIUS::acceptUNIX()
338 {
339     struct sockaddr_un addr;
340     memset(&addr, 0, sizeof(addr));
341     socklen_t size = sizeof(addr);
342     int res = accept(m_listenSocket, reinterpret_cast<sockaddr*>(&addr), &size);
343     if (res == -1)
344     {
345         m_error = std::string("Failed to accept UNIX connection: ") + strerror(errno);
346         m_logger(m_error);
347         return;
348     }
349     printfd(__FILE__, "New UNIX connection: '%s'\n", addr.sun_path);
350     m_conns.push_back(new Conn(*m_users, m_logger, *this, m_config, res, addr.sun_path));
351 }
352
353 void RADIUS::acceptTCP()
354 {
355     struct sockaddr_in addr;
356     memset(&addr, 0, sizeof(addr));
357     socklen_t size = sizeof(addr);
358     int res = accept(m_listenSocket, reinterpret_cast<sockaddr*>(&addr), &size);
359     if (res == -1)
360     {
361         m_error = std::string("Failed to accept TCP connection: ") + strerror(errno);
362         m_logger(m_error);
363         return;
364     }
365     std::string remote = inet_ntostring(addr.sin_addr.s_addr) + ":" + std::to_string(ntohs(addr.sin_port));
366     printfd(__FILE__, "New TCP connection: '%s'\n", remote.c_str());
367     m_conns.push_back(new Conn(*m_users, m_logger, *this, m_config, res, remote));
368 }
369
370 void RADIUS::authorize(const USER& user)
371 {
372     uint32_t ip = 0;
373     const std::string& login(user.GetLogin());
374     if (!m_users->Authorize(login, ip, 0xffFFffFF, this))
375     {
376         m_error = "Unable to authorize user '" + login + "' with ip " + inet_ntostring(ip) + ".";
377         m_logger(m_error);
378     }
379     else
380         m_logins.insert(login);
381 }
382
383 void RADIUS::unauthorize(const std::string& login, const std::string& reason)
384 {
385     const std::set<std::string>::const_iterator it = m_logins.find(login);
386     if (it == m_logins.end())
387         return;
388     m_logins.erase(it);
389     m_users->Unauthorize(login, this, reason);
390 }