]> git.stg.codes - stg.git/blob - projects/stargazer/plugins/other/userstat/userstat.cpp
Merge branch 'log-unauth-reasons' into full-month-stats
[stg.git] / projects / stargazer / plugins / other / userstat / userstat.cpp
1 #include <algorithm>
2 #include <cstring>
3 #include <cerrno>
4 #include <arpa/inet.h>
5 #include <csignal>
6
7 #include "common.h"
8 #include "../../../users.h"
9
10 #include "userstat.h"
11
12 BASE_PLUGIN * GetPlugin()
13 {
14 return new USERSTAT();
15 }
16
17 USERSTAT::USERSTAT()
18     : isRunning(false),
19       nonstop(false),
20       errorStr(""),
21       version(USTAT_VERSION),
22       listenSocket(-1),
23       maxThreads(16),
24       port(5555),
25       thread(0),
26       users(NULL),
27       store(NULL)
28 {
29 pthread_mutex_init(&mutex, NULL);
30 }
31
32 USERSTAT::~USERSTAT()
33 {
34 }
35
36 int USERSTAT::ParseSettings()
37 {
38 vector<PARAM_VALUE>::iterator i;
39 string s;
40
41 for(i = settings.moduleParams.begin(); i != settings.moduleParams.end(); ++i)
42     {
43     s = i->param;
44     transform(s.begin(), s.end(), s.begin(), USERSTAT::ToLower());
45     if (s == "port")
46         {
47         if (str2x<uint16_t>(*(i->value.begin()), port)) 
48             {
49             errorStr = "'Port' parameter must be a numeric value";
50             printfd(__FILE__, "USERSTAT::ParseSettings() %s\n", errorStr.c_str());
51             return -1;
52             }
53         }
54     if (s == "maxthreads")
55         {
56         if (str2x<unsigned>(*(i->value.begin()), maxThreads)) 
57             {
58             errorStr = "'MaxThreads' parameter must be a numeric value";
59             printfd(__FILE__, "USERSTAT::ParseSettings() %s\n", errorStr.c_str());
60             return -1;
61             }
62         }
63     }
64
65 return 0;
66 }
67
68 int USERSTAT::Prepare()
69 {
70 listenSocket = socket(PF_INET, SOCK_STREAM, 0);
71
72 if (listenSocket < 0)
73     {
74     errorStr = "Create USERSTAT socket failed.";
75     printfd(__FILE__, "USERSTAT::Prepare() %s\n", errorStr.c_str());
76     return -1;
77     }
78
79 printfd(__FILE__, "USERSTAT::Prepare() socket - ok\n");
80
81 listenAddr.sin_family = PF_INET;
82 listenAddr.sin_port = htons(port);
83 listenAddr.sin_addr.s_addr = inet_addr("0.0.0.0");
84
85 int lng = 1;
86
87 if (0 != setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &lng, 4))
88     {
89     errorStr = "Setsockopt failed. " + string(strerror(errno));
90     printfd(__FILE__, "USERSTAT::Prepare() %s\n", errorStr.c_str());
91     return -1;
92     }
93
94 printfd(__FILE__, "USERSTAT::Prepare() setsockopt - ok\n");
95
96 int res = bind(listenSocket, (struct sockaddr*)&listenAddr, sizeof(listenAddr));
97
98 if (res == -1)
99     {
100     errorStr = "Bind USERSTAT socket failed";
101     printfd(__FILE__, "USERSTAT::Prepare() %s\n", errorStr.c_str());
102     return -1;
103     }
104
105 printfd(__FILE__, "USERSTAT::Prepare() bind - ok port: %d\n", port);
106
107 res = listen(listenSocket, 0);
108 if (res == -1)
109     {
110     errorStr = "Listen USERSTAT socket failed";
111     printfd(__FILE__, "USERSTAT::Prepare() %s\n", errorStr.c_str());
112     return -1;
113     }
114 printfd(__FILE__, "USERSTAT::Prepare() listen - ok\n");
115
116 errorStr = "";
117 return 0;
118 }
119
120 int USERSTAT::Finalize()
121 {
122 return close(listenSocket);
123 }
124
125 int USERSTAT::Start()
126 {
127 if (users == NULL) {
128     errorStr = "Users must be set";
129     printfd(__FILE__, "USERSTAT::Start() %s\n", errorStr.c_str());
130     return -1;
131 }
132 if (store == NULL) {
133     errorStr = "Store must be set";
134     printfd(__FILE__, "USERSTAT::Start() %s\n", errorStr.c_str());
135     return -1;
136 }
137 if (Prepare())
138     {
139     return -1;
140     }
141 nonstop = true;
142 if (pthread_create(&thread, NULL, Run, this))
143     {
144     errorStr = "Cannot create thread";
145     printfd(__FILE__, "USERSTAT::Start() %s\n", errorStr.c_str());
146     return -1;
147     }
148
149 return 0;
150 }
151
152 int USERSTAT::Stop()
153 {
154 nonstop = false;
155 if (pthread_kill(thread, SIGTERM))
156     {
157     errorStr = "Cannot send signal to thread";
158     printfd(__FILE__, "USERSTAT::Stop() %s\n", errorStr.c_str());
159     return -1;
160     }
161 for (int i = 0; i < 25; i++)
162     {
163     if (!isRunning)
164         break;
165
166     usleep(200000);
167     }
168 if (isRunning)
169     {
170     errorStr = "Cannot stop thread";
171     printfd(__FILE__, "USERSTAT::Stop() %s\n", errorStr.c_str());
172     return -1;
173     }
174 return 0;
175 }
176
177 void * USERSTAT::Run(void * t)
178 {
179 USERSTAT * us = reinterpret_cast<USERSTAT *>(t);
180 pthread_t thread;
181 int outerSocket;
182 struct sockaddr_in outerAddr;
183 socklen_t outerAddrLen;
184 THREAD_INFO info;
185
186 us->isRunning = true;
187 while (us->nonstop)
188     {
189     outerSocket = accept(us->listenSocket, (struct sockaddr *)&outerAddr, &outerAddrLen); 
190     if (outerSocket > 0)
191         {
192         std::vector<THREAD_INFO>::iterator it;
193         us->pool.erase(remove_if(us->pool.begin(), us->pool.end(), USERSTAT::IsDone()), us->pool.end());
194
195         while (us->pool.size() >= us->maxThreads)
196             usleep(200000);
197         
198         info.users = us->users;
199         info.store = us->store;
200         info.outerSocket = outerSocket;
201         info.done = false;
202
203         info.request.Reset();
204
205         us->pool.push_back(info);
206         it = us->pool.end();
207         --it;
208
209         if (pthread_create(&thread, NULL, Operate, &(*it)))
210             {
211             us->errorStr = "Cannot create thread";
212             printfd(__FILE__, "USERSTAT::Run() %s\n", us->errorStr.c_str());
213             }
214         it->thread = thread;
215         }
216     }
217 us->isRunning = false;
218 return NULL;
219 }
220
221 void * USERSTAT::Operate(void * i)
222 {
223     THREAD_INFO * info = reinterpret_cast<THREAD_INFO *>(i);
224     unsigned char * buf;
225     int32_t size;
226     char * login;
227
228     int res = read(info->outerSocket, &size, sizeof(size));
229     if (res != sizeof(size))
230     {
231         printfd(__FILE__, "USERSTAT::Operate() Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res);
232         info->done = true;
233         return NULL;
234     }
235
236     printfd(__FILE__, "USERSTAT::Operate() size = %d\n", size);
237
238     if (size < 0) {
239         printfd(__FILE__, "USERSTAT::Operate() Invalid data size.\n");
240         info->done = true;
241         return NULL;
242     }
243
244     login = new char[size];
245
246     res = read(info->outerSocket, login, size);
247     if (res != size)
248     {
249         printfd(__FILE__, "USERSTAT::Operate() Reading login failed! Wanted %d bytes, got %d bytes.\n", 32, res);
250         info->done = true;
251         return NULL;
252     }
253
254     std::string l;
255     l.assign(login, size);
256
257     res = read(info->outerSocket, &size, sizeof(size));
258     if (res != sizeof(size))
259     {
260         printfd(__FILE__, "USERSTAT::Operate() Reading stream size failed! Wanted %d bytes, got %d bytes.\n", sizeof(size), res);
261         info->done = true;
262         return NULL;
263     }
264
265     printfd(__FILE__, "USERSTAT::Operate() size = %d\n", size);
266
267     if (size < 0) {
268         printfd(__FILE__, "USERSTAT::Operate() Invalid data size.\n");
269         info->done = true;
270         return NULL;
271     }
272
273     buf = new unsigned char[size];
274     res = read(info->outerSocket, buf, size);
275     if (res != size)
276     {
277         printfd(__FILE__, "USERSTAT::Operate() Reading stream failed! Wanted %d bytes, got %d bytes.\n", size, res);
278         info->done = true;
279         return NULL;
280     }
281     buf[res] = 0;
282
283     printfd(__FILE__, "USERSTAT::Operate() Received data: %s\n", buf);
284
285     if (info->users->FindByName(l, &(info->uit)))
286     {
287         printfd(__FILE__, "USERSTAT::Operate() User '%s' not found.\n", login);
288         info->done = true;
289         return NULL;
290     }
291
292     std::string password = info->uit->property.password;
293     
294     printfd(__FILE__, "USERSTAT::Operate() Requested user: '%s'\n", login);
295     printfd(__FILE__, "USERSTAT::Operate() Encription init using password: '%s'\n", password.c_str());
296
297     BLOWFISH_CTX ctx;
298     char * key = new char[password.length()];
299     strncpy(key, password.c_str(), password.length());
300
301     Blowfish_Init(&ctx,
302                   reinterpret_cast<unsigned char *>(key),
303                   password.length());
304
305     for (int i = 0; i < size / 8; ++i) {
306         uint32_t a;
307         uint32_t b;
308         a = n2l(buf + i * 8);
309         b = n2l(buf + i * 8 + 4);
310         Blowfish_Decrypt(&ctx,
311                          &a,
312                          &b);
313         l2n(a, buf + i * 8);
314         l2n(b, buf + i * 8 + 4);
315     }
316
317     delete[] key;
318
319     printfd(__FILE__, "USERSTAT::Operate() Received XML: %s\n", buf);
320
321     if (XML_Parse(info->xmlParser,
322                   reinterpret_cast<char *>(buf),
323                   size,
324                   1) != XML_STATUS_OK) {
325         printfd(__FILE__, "USERSTAT::Operate() Invalid password\n", login);
326         info->done = true;
327         delete[] buf;
328         return NULL;
329     }
330
331     if (!info->request.isOk) {
332         printfd(__FILE__, "USERSTAT::Operate() Malformed XML\n");
333         info->done = true;
334         delete[] buf;
335         return NULL;
336     }
337
338     info->Handle();
339
340     std::cout << "USERSTAT::Operate() Request:" << std::endl;
341     std::for_each(info->request.conf.begin(),
342                   info->request.conf.end(),
343                   THREAD_INFO::LinePrinter());
344     std::for_each(info->request.stat.begin(),
345                   info->request.stat.end(),
346                   THREAD_INFO::LinePrinter());
347
348     info->done = true;
349     delete[] buf;
350     return NULL;
351 }
352
353 void TIParseXMLStart(void * data, const char * name, const char ** attr)
354 {
355     THREAD_INFO * ti = reinterpret_cast<THREAD_INFO *>(data);
356     if (strncmp(name, "request", 7) == 0) {
357         if (attr == NULL) {
358             printfd(__FILE__, "ParseXMLStart() 'reqest' tag require a 'login' parameter\n");
359             ti->request.isOk |= false;
360             return;
361         } else {
362             ti->request.login = *attr;
363         }
364     } else if (strncmp(name, "stat", 4)) {
365         ti->pvList = &(ti->request.stat);
366     } else if (strncmp(name, "conf", 4)) {
367         ti->pvList = &(ti->request.conf);
368     } else {
369         if (ti->pvList == NULL) {
370             printfd(__FILE__, "ParseXMLStart() Unexpected tag: '%s'\n", name);
371             ti->request.isOk |= false;
372             return;
373         }
374         (*ti->pvList)[name];
375     }
376 }
377
378 void TIParseXMLEnd(void * data, const char * name)
379 {
380     THREAD_INFO * ti = reinterpret_cast<THREAD_INFO *>(data);
381     if (strncmp(name, "stat", 4) == 0) {
382         ti->pvList = NULL;
383     } else if (strncmp(name, "conf", 4) == 0) {
384         ti->pvList = NULL;
385     } else if (strncmp(name, "request", 7) == 0) {
386     }
387 }
388
389 THREAD_INFO::THREAD_INFO() : pvList(NULL),
390                 users(NULL),
391                 store(NULL),
392                 outerSocket(-1),
393                 done(true)
394 {
395     printfd(__FILE__, "THREAD_INFO::THREAD_INFO()\n");
396     xmlParser = XML_ParserCreate(NULL);
397
398     if (!xmlParser)
399         {
400         printfd(__FILE__, "USERSTAT::Run() Couldn't allocate memory for parser\n");
401         }
402
403     XML_ParserReset(xmlParser, NULL);
404     XML_SetElementHandler(xmlParser, TIParseXMLStart, TIParseXMLEnd);
405     XML_SetUserData(xmlParser, this);
406 }
407
408 THREAD_INFO::~THREAD_INFO()
409 {
410     printfd(__FILE__, "THREAD_INFO::~THREAD_INFO()\n");
411     XML_ParserFree(xmlParser);
412 }
413
414 int THREAD_INFO::Handle()
415 {
416     if (!request.isOk)
417         return -1;
418
419     if (HandleStat())
420         return -1;
421
422     if (HandleConf())
423         return -1;
424
425     return 0;
426 }
427
428 int THREAD_INFO::HandleConf()
429 {
430     PV_LIST::iterator it(request.conf.begin());
431
432     for (; it != request.conf.end(); ++it)
433         {
434         if (it->first == "password")
435             {
436             it->second = uit->property.password;
437             }
438         else if (it->first == "passive")
439             {
440             it->second = uit->property.passive;
441             }
442         else if (it->first == "disabled")
443             {
444             it->second = uit->property.disabled;
445             }
446         else if (it->first == "disabledDetailStat")
447             {
448             it->second = uit->property.disabledDetailStat;
449             }
450         else if (it->first == "alwaysOnline")
451             {
452             it->second = uit->property.alwaysOnline;
453             }
454         else if (it->first == "tariffName")
455             {
456             it->second = uit->property.tariffName;
457             }
458         else if (it->first == "address")
459             {
460             it->second = uit->property.address;
461             }
462         else if (it->first == "phone")
463             {
464             it->second = uit->property.phone;
465             }
466         else if (it->first == "email")
467             {
468             it->second = uit->property.email;
469             }
470         else if (it->first == "note")
471             {
472             it->second = uit->property.note;
473             }
474         else if (it->first == "realName")
475             {
476             it->second = uit->property.realName;
477             }
478         else if (it->first == "group")
479             {
480             it->second = uit->property.group;
481             }
482         else if (it->first == "credit")
483             {
484             it->second = uit->property.credit;
485             }
486         else if (it->first == "creditExpire")
487             {
488             it->second = uit->property.creditExpire;
489             }
490         else if (it->first == "nextTariff")
491             {
492             it->second = uit->property.nextTariff;
493             }
494         else
495             {
496             printfd(__FILE__, "THREAD_INFO::HandleConf() Invalid param: '%s'\n", it->first.c_str());
497             }
498         }
499
500     return 0;
501 }
502
503 int THREAD_INFO::HandleStat()
504 {
505     PV_LIST::iterator it(request.conf.begin());
506
507     for (; it != request.conf.end(); ++it)
508         {
509         if (it->first == "cash")
510             {
511             it->second = uit->property.password;
512             }
513         else
514             {
515             printfd(__FILE__, "THREAD_INFO::HandleConf() Invalid param: '%s'\n", it->first.c_str());
516             }
517         }
518
519     return 0;
520 }