]> git.stg.codes - stg.git/blob - projects/stargazer/plugins/other/radius/radius.cpp
Implemented backend for rlm_stg.
[stg.git] / projects / 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
27 #include <algorithm>
28 #include <stdexcept>
29 #include <csignal>
30 #include <cerrno>
31 #include <cstring>
32
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/un.h> // UNIX
36 #include <netinet/in.h> // IP
37 #include <netinet/tcp.h> // TCP
38 #include <netdb.h>
39
40 using STG::Config;
41 using STG::Conn;
42
43 namespace
44 {
45
46 PLUGIN_CREATOR<RADIUS> creator;
47
48 }
49
50 extern "C" PLUGIN * GetPlugin()
51 {
52     return creator.GetPlugin();
53 }
54
55 RADIUS::RADIUS()
56     : m_config(m_settings),
57       m_running(false),
58       m_stopped(true),
59       m_users(NULL),
60       m_store(NULL),
61       m_listenSocket(0),
62       m_logger(GetPluginLogger(GetStgLogger(), "radius"))
63 {
64 }
65
66 int RADIUS::ParseSettings()
67 {
68     try {
69         m_config = STG::Config(m_settings);
70         return reconnect() ? 0 : -1;
71     } catch (const std::runtime_error& ex) {
72         m_logger("Failed to parse settings. %s", ex.what());
73         return -1;
74     }
75 }
76
77 int RADIUS::Start()
78 {
79     if (m_running)
80         return 0;
81
82     int res = pthread_create(&m_thread, NULL, run, this);
83     if (res == 0)
84         return 0;
85
86     m_error = strerror(res);
87     m_logger("Failed to create thread: '" + m_error + "'.");
88     return -1;
89 }
90
91 int RADIUS::Stop()
92 {
93     if (m_stopped)
94         return 0;
95
96     m_running = false;
97
98     for (size_t i = 0; i < 25 && !m_stopped; i++) {
99         struct timespec ts = {0, 200000000};
100         nanosleep(&ts, NULL);
101     }
102
103     if (m_stopped) {
104         pthread_join(m_thread, NULL);
105         return 0;
106     }
107
108     m_error = "Failed to stop thread.";
109     m_logger(m_error);
110     return -1;
111 }
112 //-----------------------------------------------------------------------------
113 void* RADIUS::run(void* d)
114 {
115     sigset_t signalSet;
116     sigfillset(&signalSet);
117     pthread_sigmask(SIG_BLOCK, &signalSet, NULL);
118
119     static_cast<RADIUS *>(d)->runImpl();
120
121     return NULL;
122 }
123
124 bool RADIUS::reconnect()
125 {
126     if (!m_conns.empty())
127     {
128         std::deque<STG::Conn *>::const_iterator it;
129         for (it = m_conns.begin(); it != m_conns.end(); ++it)
130             delete(*it);
131         m_conns.clear();
132     }
133     if (m_listenSocket != 0)
134     {
135         shutdown(m_listenSocket, SHUT_RDWR);
136         close(m_listenSocket);
137         if (m_config.connectionType == Config::UNIX)
138             unlink(m_config.bindAddress.c_str());
139     }
140     if (m_config.connectionType == Config::UNIX)
141         m_listenSocket = createUNIX();
142     else
143         m_listenSocket = createTCP();
144     if (m_listenSocket == 0)
145         return false;
146     if (listen(m_listenSocket, 100) == -1)
147     {
148         m_error = std::string("Error starting to listen socket: ") + strerror(errno);
149         m_logger(m_error);
150         return false;
151     }
152     return true;
153 }
154
155 int RADIUS::createUNIX() const
156 {
157     int fd = socket(AF_UNIX, SOCK_STREAM, 0);
158     if (fd == -1)
159     {
160         m_error = std::string("Error creating UNIX socket: ") + strerror(errno);
161         m_logger(m_error);
162         return 0;
163     }
164     struct sockaddr_un addr;
165     memset(&addr, 0, sizeof(addr));
166     addr.sun_family = AF_UNIX;
167     strncpy(addr.sun_path, m_config.bindAddress.c_str(), m_config.bindAddress.length());
168     if (bind(fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
169     {
170         shutdown(fd, SHUT_RDWR);
171         close(fd);
172         m_error = std::string("Error binding UNIX socket: ") + strerror(errno);
173         m_logger(m_error);
174         return 0;
175     }
176     return fd;
177 }
178
179 int RADIUS::createTCP() const
180 {
181     addrinfo hints;
182     memset(&hints, 0, sizeof(addrinfo));
183
184     hints.ai_family = AF_INET;       /* Allow IPv4 */
185     hints.ai_socktype = SOCK_STREAM; /* Stream socket */
186     hints.ai_flags = AI_PASSIVE;     /* For wildcard IP address */
187     hints.ai_protocol = 0;           /* Any protocol */
188     hints.ai_canonname = NULL;
189     hints.ai_addr = NULL;
190     hints.ai_next = NULL;
191
192     addrinfo* ais = NULL;
193     int res = getaddrinfo(m_config.bindAddress.c_str(), m_config.portStr.c_str(), &hints, &ais);
194     if (res != 0)
195     {
196         m_error = "Error resolvin address '" + m_config.bindAddress + "': " + gai_strerror(res);
197         m_logger(m_error);
198         return 0;
199     }
200
201     for (addrinfo* ai = ais; ai != NULL; ai = ai->ai_next)
202     {
203         int fd = socket(AF_INET, SOCK_STREAM, 0);
204         if (fd == -1)
205         {
206             m_error = std::string("Error creating TCP socket: ") + strerror(errno);
207             m_logger(m_error);
208             freeaddrinfo(ais);
209             return 0;
210         }
211         if (bind(fd, ai->ai_addr, ai->ai_addrlen) == -1)
212         {
213             shutdown(fd, SHUT_RDWR);
214             close(fd);
215             m_error = std::string("Error binding TCP socket: ") + strerror(errno);
216             m_logger(m_error);
217             continue;
218         }
219         freeaddrinfo(ais);
220         return fd;
221     }
222
223     m_error = "Failed to resolve '" + m_config.bindAddress;
224     m_logger(m_error);
225
226     freeaddrinfo(ais);
227     return 0;
228 }
229
230 void RADIUS::runImpl()
231 {
232     m_running = true;
233
234     while (m_running) {
235         fd_set fds;
236
237         buildFDSet(fds);
238
239         struct timeval tv;
240         tv.tv_sec = 0;
241         tv.tv_usec = 500000;
242
243         int res = select(maxFD() + 1, &fds, NULL, NULL, &tv);
244         if (res < 0)
245         {
246             m_error = std::string("'select' is failed: '") + strerror(errno) + "'.";
247             m_logger(m_error);
248             break;
249         }
250
251         if (!m_running)
252             break;
253
254         if (res > 0)
255             handleEvents(fds);
256         else
257         {
258             for (std::deque<Conn*>::iterator it = m_conns.begin(); it != m_conns.end(); ++it)
259                 (*it)->tick();
260         }
261
262         cleanupConns();
263     }
264
265     m_stopped = true;
266 }
267
268 int RADIUS::maxFD() const
269 {
270     int maxFD = m_listenSocket;
271     std::deque<STG::Conn *>::const_iterator it;
272     for (it = m_conns.begin(); it != m_conns.end(); ++it)
273         if (maxFD < (*it)->sock())
274             maxFD = (*it)->sock();
275     return maxFD;
276 }
277
278 void RADIUS::buildFDSet(fd_set & fds) const
279 {
280     FD_ZERO(&fds);
281     FD_SET(m_listenSocket, &fds);
282     std::deque<STG::Conn *>::const_iterator it;
283     for (it = m_conns.begin(); it != m_conns.end(); ++it)
284         FD_SET((*it)->sock(), &fds);
285 }
286
287 void RADIUS::cleanupConns()
288 {
289     std::deque<STG::Conn *>::iterator pos;
290     for (pos = m_conns.begin(); pos != m_conns.end(); ++pos)
291         if (!(*pos)->isOk()) {
292             delete *pos;
293             *pos = NULL;
294         }
295
296     pos = std::remove(m_conns.begin(), m_conns.end(), static_cast<STG::Conn *>(NULL));
297     m_conns.erase(pos, m_conns.end());
298 }
299
300 void RADIUS::handleEvents(const fd_set & fds)
301 {
302     if (FD_ISSET(m_listenSocket, &fds))
303         acceptConnection();
304     else
305     {
306         std::deque<STG::Conn *>::iterator it;
307         for (it = m_conns.begin(); it != m_conns.end(); ++it)
308             if (FD_ISSET((*it)->sock(), &fds))
309                 (*it)->read();
310             else
311                 (*it)->tick();
312     }
313 }
314
315 void RADIUS::acceptConnection()
316 {
317     if (m_config.connectionType == Config::UNIX)
318         acceptUNIX();
319     else
320         acceptTCP();
321 }
322
323 void RADIUS::acceptUNIX()
324 {
325     struct sockaddr_un addr;
326     memset(&addr, 0, sizeof(addr));
327     socklen_t size = sizeof(addr);
328     int res = accept(m_listenSocket, reinterpret_cast<sockaddr*>(&addr), &size);
329     if (res == -1)
330     {
331         m_error = std::string("Failed to accept UNIX connection: ") + strerror(errno);
332         m_logger(m_error);
333         return;
334     }
335     m_conns.push_back(new Conn(*m_users, m_logger, m_config, res, addr.sun_path));
336 }
337
338 void RADIUS::acceptTCP()
339 {
340     struct sockaddr_in addr;
341     memset(&addr, 0, sizeof(addr));
342     socklen_t size = sizeof(addr);
343     int res = accept(m_listenSocket, reinterpret_cast<sockaddr*>(&addr), &size);
344     if (res == -1)
345     {
346         m_error = std::string("Failed to accept TCP connection: ") + strerror(errno);
347         m_logger(m_error);
348         return;
349     }
350     std::string remote = inet_ntostring(addr.sin_addr.s_addr) + ":" + x2str(ntohs(addr.sin_port));
351     m_conns.push_back(new Conn(*m_users, m_logger, m_config, res, remote));
352 }