reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
RestAdmin.cxx
Go to the documentation of this file.
1#include "rutil/ResipAssert.h"
2
3#if defined(HAVE_CONFIG_H)
4 #include "config.h"
5#endif
6
7#include <chrono>
8#include <sstream>
9
10#include "cajun/json/elements.h"
11#include "cajun/json/writer.h"
12
18#include "resip/stack/Tuple.hxx"
19#include "rutil/Data.hxx"
20#include "rutil/DnsUtil.hxx"
21#include "rutil/Lock.hxx"
22#include "rutil/Logger.hxx"
23#include "rutil/ParseBuffer.hxx"
24#include "rutil/Socket.hxx"
25#include "rutil/Timer.hxx"
27
28#include "repro/AclStore.hxx"
29#include "repro/FilterStore.hxx"
30#include "repro/Proxy.hxx"
31#include "repro/RestAdmin.hxx"
32#include "repro/RouteStore.hxx"
33#include "repro/Store.hxx"
34#include "repro/UserStore.hxx"
35#include "repro/WebAdmin.hxx"
36
37#ifdef USE_SSL
39#endif
40
41using namespace resip;
42using namespace repro;
43using namespace std;
44
45#define RESIPROCATE_SUBSYSTEM Subsystem::REPRO
46
47namespace
48{
49// Small helper: render a cajun element to a resip::Data.
50Data writeJson(const json::UnknownElement& elem)
51{
52 std::ostringstream oss;
53 json::Writer::Write(elem, oss);
54 std::string s = oss.str();
55 return Data(s.c_str(), s.size());
56}
57
58// Build a standard success envelope: {"data": <payload>}
59Data successEnvelope(const json::UnknownElement& payload)
60{
61 json::Object obj;
62 obj["data"] = payload;
63 return writeJson(obj);
64}
65
66// Build an empty success: {"status":"ok"}
67Data okEnvelope()
68{
69 json::Object obj;
70 obj["status"] = json::String("ok");
71 return writeJson(obj);
72}
73
74// Build an error envelope: {"error":{"code":N,"message":"..."}}
75Data errorEnvelope(int code, const Data& message)
76{
77 json::Object err;
78 err["code"] = json::Number(code);
79 err["message"] = json::String(message.c_str());
80 json::Object obj;
81 obj["error"] = err;
82 return writeJson(obj);
83}
84
85// Small helper: turn an unsigned int into a JSON string key.
86// Used for maps keyed by SIP response codes (JSON object keys must be strings).
87std::string codeToKey(unsigned int code)
88{
89 std::ostringstream oss;
90 oss << code;
91 return oss.str();
92}
93
94// SIP method types tracked by the StatisticsMessage per-method arrays.
95// We enumerate them explicitly so we can map each array slot to a
96// human-readable name without depending on a Method<->String helper that
97// might not be available in all resip builds.
98struct MethodNameEntry
99{
101 const char* name;
102};
103const MethodNameEntry kTrackedMethods[] =
104{
105 { resip::INVITE, "INVITE" },
106 { resip::ACK, "ACK" },
107 { resip::BYE, "BYE" },
108 { resip::CANCEL, "CANCEL" },
109 { resip::MESSAGE, "MESSAGE" },
110 { resip::OPTIONS, "OPTIONS" },
111 { resip::REGISTER, "REGISTER" },
112 { resip::PUBLISH, "PUBLISH" },
113 { resip::SUBSCRIBE, "SUBSCRIBE" },
114 { resip::NOTIFY, "NOTIFY" },
115 { resip::REFER, "REFER" },
116 { resip::INFO, "INFO" },
117 { resip::PRACK, "PRACK" },
118 { resip::SERVICE, "SERVICE" },
119 { resip::UPDATE, "UPDATE" },
120};
121const size_t kNumTrackedMethods = sizeof(kTrackedMethods) / sizeof(kTrackedMethods[0]);
122
123// Build a sparse JSON object from a responsesByCode-style array.
124// Only includes codes whose count is non-zero.
125json::Object responseCodeMap(const unsigned int* codeArr,
126 unsigned int maxCode)
127{
128 json::Object obj;
129 for (unsigned int c = 0; c < maxCode; ++c)
130 {
131 if (codeArr[c] != 0)
132 {
133 obj[codeToKey(c)] = json::Number(codeArr[c]);
134 }
135 }
136 return obj;
137}
138
139// Build a sparse JSON object keyed by method name for a *ByMethod array.
140// Only includes methods whose count is non-zero.
141json::Object methodMap(const unsigned int* methodArr)
142{
143 json::Object obj;
144 for (size_t i = 0; i < kNumTrackedMethods; ++i)
145 {
146 unsigned int v = methodArr[kTrackedMethods[i].type];
147 if (v != 0)
148 {
149 obj[kTrackedMethods[i].name] = json::Number(v);
150 }
151 }
152 return obj;
153}
154
155// Build a nested sparse JSON object for a *ByMethodByCode 2-D array.
156// Outer keys are method names, inner keys are response codes.
157// Methods with no non-zero codes are omitted entirely.
158json::Object methodCodeMap(const unsigned int (*arr)[resip::StatisticsMessage::Payload::MaxCode])
159{
160 json::Object obj;
161 for (size_t i = 0; i < kNumTrackedMethods; ++i)
162 {
163 resip::MethodTypes m = kTrackedMethods[i].type;
164 json::Object inner = responseCodeMap(
166 // Only include the method if it has any non-zero codes.
167 if (!inner.Empty())
168 {
169 obj[kTrackedMethods[i].name] = inner;
170 }
171 }
172 return obj;
173}
174
175// Turn a StatisticsMessage::Payload into a json::Object with one entry per
176// counter / sparse sub-object per array. Only response codes and methods
177// that have non-zero counts are included, to keep payload size bounded.
178json::Object payloadToJson(const resip::StatisticsMessage::Payload& p)
179{
180 json::Object obj;
181
182 // --- Scalar counters (mirror exactly) ---
183 obj["tuFifoSize"] = json::Number(p.tuFifoSize);
184 obj["transportFifoSizeSum"] = json::Number(p.transportFifoSizeSum);
185 obj["transactionFifoSize"] = json::Number(p.transactionFifoSize);
186 obj["activeTimers"] = json::Number(p.activeTimers);
187 obj["openTcpConnections"] = json::Number(p.openTcpConnections);
188 obj["activeClientTransactions"] = json::Number(p.activeClientTransactions);
189 obj["activeServerTransactions"] = json::Number(p.activeServerTransactions);
190 obj["pendingDnsQueries"] = json::Number(p.pendingDnsQueries);
191
192 obj["requestsSent"] = json::Number(p.requestsSent);
193 obj["responsesSent"] = json::Number(p.responsesSent);
194 obj["requestsRetransmitted"] = json::Number(p.requestsRetransmitted);
195 obj["responsesRetransmitted"] = json::Number(p.responsesRetransmitted);
196 obj["requestsReceived"] = json::Number(p.requestsReceived);
197 obj["responsesReceived"] = json::Number(p.responsesReceived);
198
199 // --- Sparse response-code histogram ---
200 obj["responsesByCode"] =
201 responseCodeMap(p.responsesByCode,
203
204 // --- Per-method counters (sparse, keyed by method name) ---
205 obj["requestsSentByMethod"] = methodMap(p.requestsSentByMethod);
206 obj["requestsRetransmittedByMethod"] = methodMap(p.requestsRetransmittedByMethod);
207 obj["requestsReceivedByMethod"] = methodMap(p.requestsReceivedByMethod);
208 obj["responsesSentByMethod"] = methodMap(p.responsesSentByMethod);
209 obj["responsesRetransmittedByMethod"] = methodMap(p.responsesRetransmittedByMethod);
210 obj["responsesReceivedByMethod"] = methodMap(p.responsesReceivedByMethod);
211
212 // --- Per-method per-code response histograms (2-level sparse) ---
213 obj["responsesSentByMethodByCode"] = methodCodeMap(p.responsesSentByMethodByCode);
214 obj["responsesRetransmittedByMethodByCode"] = methodCodeMap(p.responsesRetransmittedByMethodByCode);
215 obj["responsesReceivedByMethodByCode"] = methodCodeMap(p.responsesReceivedByMethodByCode);
216
217 return obj;
218}
219}
220
221
223 : mWebAdmin(webAdmin)
224{
225}
226
230
231void
232RestAdmin::parseRequest(const Data& uri,
233 std::vector<Data>& pathSegments,
234 ParamMap& queryParams) const
235{
236 pathSegments.clear();
237 queryParams.clear();
238
239 // Split URI into path portion and query portion.
240 Data pathPart;
241 Data queryPart;
242 Data::size_type q = uri.find("?");
243 if (q == Data::npos)
244 {
245 pathPart = uri;
246 }
247 else
248 {
249 pathPart = uri.substr(0, q);
250 queryPart = uri.substr(q + 1);
251 }
252
253 // Strip the /api/v1 prefix so remaining path is e.g. "users/alice@example.com".
254 const Data prefix("/api/v1");
255 if (pathPart.prefix(prefix))
256 {
257 pathPart = pathPart.substr(prefix.size());
258 }
259 // Strip leading slash from what remains.
260 if (!pathPart.empty() && pathPart.data()[0] == '/')
261 {
262 pathPart = pathPart.substr(1);
263 }
264
265 // Split path on '/'. Each segment is URL-decoded.
266 if (!pathPart.empty())
267 {
268 ParseBuffer pb(pathPart);
269 while (!pb.eof())
270 {
271 const char* anchor = pb.position();
272 pb.skipToChar('/');
273 Data seg;
274 pb.data(seg, anchor);
275 pathSegments.push_back(seg.urlDecoded());
276 if (!pb.eof())
277 {
278 pb.skipChar('/');
279 }
280 }
281 }
282
283 // Parse query string "k=v&k=v". Values URL-decoded; keys kept raw
284 // (they're simple ASCII names in practice).
285 if (!queryPart.empty())
286 {
287 ParseBuffer pb(queryPart);
288 while (!pb.eof())
289 {
290 const char* anchor1 = pb.position();
291 pb.skipToChar('=');
292 Data key;
293 pb.data(key, anchor1);
294
295 Data value;
296 if (!pb.eof())
297 {
298 const char* anchor2 = pb.skipChar('=');
299 pb.skipToChar('&');
300 pb.data(value, anchor2);
301 }
302
303 if (!pb.eof())
304 {
305 pb.skipChar('&');
306 }
307
308 if (!key.empty())
309 {
310 queryParams[key] = value.urlDecoded();
311 }
312 }
313 }
314}
315
316void
317RestAdmin::sendJson(int pageNumber, int statusCode, const Data& json)
318{
319 mWebAdmin.setApiResponse(pageNumber, statusCode, json);
320}
321
322void
323RestAdmin::sendOk(int pageNumber)
324{
325 sendJson(pageNumber, 200, okEnvelope());
326}
327
328void
329RestAdmin::sendError(int pageNumber, int statusCode, const Data& message)
330{
331 sendJson(pageNumber, statusCode, errorEnvelope(statusCode, message));
332}
333
334void
335RestAdmin::sendMethodNotAllowed(int pageNumber, const Data& method)
336{
337 sendError(pageNumber, 405, Data("Method not allowed: ") + method);
338}
339
340void
341RestAdmin::sendNotFound(int pageNumber)
342{
343 sendJson(pageNumber, 404, errorEnvelope(404, "Not found"));
344}
345
346Data
347RestAdmin::param(const ParamMap& q, const Data& key, const Data& defaultValue)
348{
349 ParamMap::const_iterator it = q.find(key);
350 if (it == q.end())
351 {
352 return defaultValue;
353 }
354 return it->second;
355}
356
357bool
358RestAdmin::hasParam(const ParamMap& q, const Data& key)
359{
360 return q.find(key) != q.end();
361}
362
363void
365 const Data& uri,
366 int pageNumber,
367 const Data& /*authenticatedUser*/)
368{
369 std::vector<Data> path;
370 ParamMap query;
371 parseRequest(uri, path, query);
372
373 DebugLog(<< "RestAdmin dispatch: method=" << method << " uri=" << uri
374 << " segments=" << path.size());
375
376 if (path.empty())
377 {
378 sendNotFound(pageNumber);
379 return;
380 }
381
382 const Data& resource = path[0];
383
384 if (resource == "registrations")
385 {
386 handleRegistrations(method, path, query, pageNumber);
387 }
388 else if (resource == "publications")
389 {
390 handlePublications(method, path, query, pageNumber);
391 }
392 else if (resource == "domains")
393 {
394 handleDomains(method, path, query, pageNumber);
395 }
396 else if (resource == "users")
397 {
398 handleUsers(method, path, query, pageNumber);
399 }
400 else if (resource == "acls")
401 {
402 handleAcls(method, path, query, pageNumber);
403 }
404 else if (resource == "routes")
405 {
406 handleRoutes(method, path, query, pageNumber);
407 }
408 else if (resource == "filters")
409 {
410 handleFilters(method, path, query, pageNumber);
411 }
412 else if (resource == "settings")
413 {
414 handleSettings(method, pageNumber);
415 }
416 else if (resource == "stackinfo")
417 {
418 handleStackInfo(method, pageNumber);
419 }
420 else if (resource == "stats")
421 {
422 handleStats(method, path, pageNumber);
423 }
424 else if (resource == "congestion")
425 {
426 handleCongestion(method, pageNumber);
427 }
428 else if (resource == "loglevel")
429 {
430 handleLogLevel(method, query, pageNumber);
431 }
432 else if (resource == "dnscache")
433 {
434 handleDnsCache(method, path, pageNumber);
435 }
436 else if (resource == "restart")
437 {
438 handleRestart(method, pageNumber);
439 }
440 else if (resource == "certs")
441 {
442 handleCertsReload(method, pageNumber);
443 }
444 else
445 {
446 sendNotFound(pageNumber);
447 }
448}
449
450// ---------------------------------------------------------------------------
451// Registrations
452// ---------------------------------------------------------------------------
453void
454RestAdmin::handleRegistrations(const Data& method,
455 const std::vector<Data>& path,
456 const ParamMap& /*query*/,
457 int pageNumber)
458{
459 RegistrationPersistenceManager& regDb = mWebAdmin.mRegDb;
460
461 if (method == "GET" && path.size() == 1)
462 {
464 json::Array aorArr;
465
467 regDb.getAors(aors);
468 for (RegistrationPersistenceManager::UriList::const_iterator aorIt = aors.begin();
469 aorIt != aors.end(); ++aorIt)
470 {
471 Uri aor = *aorIt;
472 ContactList contacts;
473 regDb.getContacts(aor, contacts);
474
475 json::Array contactArr;
476 for (ContactList::iterator i = contacts.begin();
477 i != contacts.end(); ++i)
478 {
479 if (i->mRegExpires > now)
480 {
481 const ContactInstanceRecord& r = *i;
482 json::Object c;
483 c["contact"] = json::String(Data::from(r.mContact.uri()).c_str());
484 c["userAgent"] = json::String(r.mUserAgent.c_str());
485 c["instanceId"] = json::String(r.mInstance.c_str());
486 c["regId"] = json::Number(r.mRegId);
487 if (r.mContact.exists(p_q))
488 {
489#ifdef RESIP_FIXED_POINT
490 c["qValue"] = json::Number(r.mContact.param(p_q));
491#else
492 c["qValue"] = json::Number(r.mContact.param(p_q).floatVal());
493#endif
494 }
495 json::Array pathArr;
496 for (NameAddrs::const_iterator naIt = r.mSipPath.begin();
497 naIt != r.mSipPath.end(); ++naIt)
498 {
499 pathArr.Insert(json::String(Data::from(naIt->uri()).c_str()));
500 }
501 c["path"] = pathArr;
502 c["synced"] = json::Boolean(r.mSyncContact);
503 bool staticRegContact = (r.mRegExpires == NeverExpire);
504 c["static"] = json::Boolean(staticRegContact);
505 if (staticRegContact)
506 {
507 c["expiresIn"] = json::Null();
508 }
509 else
510 {
511 c["expiresIn"] = json::Number((double)(r.mRegExpires - now));
512 }
513 contactArr.Insert(c);
514 }
515 else
516 {
517 // Opportunistically remove expired contacts, matching HTML behavior.
518 regDb.removeContact(aor, *i);
519 }
520 }
521
522 json::Object aorObj;
523 aorObj["aor"] = json::String(Data::from(aor).c_str());
524 aorObj["contacts"] = contactArr;
525 aorArr.Insert(aorObj);
526 }
527
528 sendJson(pageNumber, 200, successEnvelope(aorArr));
529 return;
530 }
531
532 if (method == "DELETE" && path.size() == 2)
533 {
534 // DELETE /api/v1/registrations/{aor} -> remove all contacts for this AOR
535 const Data& aorStr = path[1];
536 try
537 {
538 Uri aor(aorStr);
539 ContactList contacts;
540 regDb.getContacts(aor, contacts);
541 int removed = 0;
542 for (ContactList::iterator i = contacts.begin(); i != contacts.end(); ++i)
543 {
544 regDb.removeContact(aor, *i);
545 // If this was a static registration, also remove from the store.
546 if (i->mRegExpires == NeverExpire)
547 {
548 mWebAdmin.mStore.mStaticRegStore.eraseStaticReg(aor, i->mContact);
549 }
550 ++removed;
551 }
552 json::Object payload;
553 payload["removed"] = json::Number(removed);
554 payload["aor"] = json::String(aorStr.c_str());
555 sendJson(pageNumber, 200, successEnvelope(payload));
556 }
557 catch (BaseException& e)
558 {
559 WarningLog(<< "REST delete registration: bad AOR " << aorStr << ": " << e);
560 sendError(pageNumber, 400, Data("Invalid AOR: ") + aorStr);
561 }
562 return;
563 }
564
565 sendMethodNotAllowed(pageNumber, method);
566}
567
568// ---------------------------------------------------------------------------
569// Publications
570// ---------------------------------------------------------------------------
571void
572RestAdmin::handlePublications(const Data& method,
573 const std::vector<Data>& path,
574 const ParamMap& /*query*/,
575 int pageNumber)
576{
577 PublicationPersistenceManager& pubDb = mWebAdmin.mPubDb;
578
579 if (method == "GET" && path.size() == 1)
580 {
582 json::Array arr;
583
584 pubDb.lockDocuments();
586 for (PublicationPersistenceManager::KeyToETagMap::iterator keyIt = publications.begin();
587 keyIt != publications.end(); ++keyIt)
588 {
589 for (PublicationPersistenceManager::ETagToDocumentMap::iterator eTagIt = keyIt->second.begin();
590 eTagIt != keyIt->second.end(); ++eTagIt)
591 {
592 if (eTagIt->second.mExpirationTime > now)
593 {
594 json::Object pubObj;
595 pubObj["aor"] = json::String(eTagIt->second.mDocumentKey.c_str());
596 pubObj["eventType"] = json::String(eTagIt->second.mEventType.c_str());
597 pubObj["eTag"] = json::String(eTagIt->second.mETag.c_str());
598
599 GenericPidfContents* pidf = dynamic_cast<GenericPidfContents*>(eTagIt->second.mContents.get());
600 if (pidf)
601 {
602 Data summary;
603 summary += (pidf->getSimplePresenceOnline() ? "open" : "closed");
604 if (!pidf->getSimplePresenceNote().empty())
605 {
606 summary += " - ";
607 summary += pidf->getSimplePresenceNote();
608 }
609 pubObj["data"] = json::String(summary.c_str());
610 }
611 else
612 {
613 pubObj["data"] = json::Null();
614 }
615
616 pubObj["synced"] = json::Boolean(eTagIt->second.mSyncPublication);
617 pubObj["expiresIn"] = json::Number((double)(eTagIt->second.mExpirationTime - now));
618 arr.Insert(pubObj);
619 }
620 }
621 }
622 pubDb.unlockDocuments();
623
624 sendJson(pageNumber, 200, successEnvelope(arr));
625 return;
626 }
627
628 sendMethodNotAllowed(pageNumber, method);
629}
630
631// ---------------------------------------------------------------------------
632// Domains
633// ---------------------------------------------------------------------------
634void
635RestAdmin::handleDomains(const Data& method,
636 const std::vector<Data>& path,
637 const ParamMap& query,
638 int pageNumber)
639{
640 ConfigStore& configStore = mWebAdmin.mStore.mConfigStore;
641
642 if (method == "GET" && path.size() == 1)
643 {
644 json::Array arr;
645 const ConfigStore::ConfigData& configs = configStore.getConfigs();
646 for (ConfigStore::ConfigData::const_iterator i = configs.begin();
647 i != configs.end(); ++i)
648 {
649 json::Object obj;
650 obj["domain"] = json::String(i->second.mDomain.c_str());
651 obj["tlsPort"] = json::Number(i->second.mTlsPort);
652 arr.Insert(obj);
653 }
654 sendJson(pageNumber, 200, successEnvelope(arr));
655 return;
656 }
657
658 if (method == "POST" && path.size() == 1)
659 {
660 if (!hasParam(query, "uri"))
661 {
662 sendError(pageNumber, 400, "Missing required parameter: uri");
663 return;
664 }
665 Data uri = param(query, "uri");
666 int tlsPort = param(query, "tlsPort", "0").convertInt();
667 if (configStore.addDomain(uri, tlsPort))
668 {
669 json::Object obj;
670 obj["domain"] = json::String(uri.c_str());
671 obj["tlsPort"] = json::Number(tlsPort);
672 sendJson(pageNumber, 200, successEnvelope(obj));
673 }
674 else
675 {
676 sendError(pageNumber, 500, "Failed to add domain (database error)");
677 }
678 return;
679 }
680
681 if (method == "DELETE" && path.size() == 2)
682 {
683 const Data& domain = path[1];
684 configStore.eraseDomain(domain);
685 sendOk(pageNumber);
686 return;
687 }
688
689 sendMethodNotAllowed(pageNumber, method);
690}
691
692// ---------------------------------------------------------------------------
693// Users
694// ---------------------------------------------------------------------------
695void
696RestAdmin::handleUsers(const Data& method,
697 const std::vector<Data>& path,
698 const ParamMap& query,
699 int pageNumber)
700{
701 UserStore& userStore = mWebAdmin.mStore.mUserStore;
702
703 // Helper lambda to build a JSON object for a user record
704 auto userToJson = [](const AbstractDb::UserRecord& rec) -> json::Object {
705 json::Object obj;
706 obj["user"] = json::String(rec.user.c_str());
707 obj["domain"] = json::String(rec.domain.c_str());
708 obj["realm"] = json::String(rec.realm.c_str());
709 obj["name"] = json::String(rec.name.c_str());
710 obj["email"] = json::String(rec.email.c_str());
711 return obj;
712 };
713
714 // GET /api/v1/users -> list all
715 if (method == "GET" && path.size() == 1)
716 {
717 json::Array arr;
718 int count = 0;
719 UserStore::Key key = userStore.getFirstKey();
720 while (!key.empty())
721 {
722 AbstractDb::UserRecord rec = userStore.getUserInfo(key);
723 json::Object obj = userToJson(rec);
724 // include the AOR-style identifier clients can use in PUT/DELETE
725 obj["id"] = json::String((rec.user + Data("@") + rec.domain).c_str());
726 arr.Insert(obj);
727
728 if (++count > 10000) // safety cap
729 {
730 break;
731 }
732 key = userStore.getNextKey();
733 }
734 sendJson(pageNumber, 200, successEnvelope(arr));
735 return;
736 }
737
738 // POST /api/v1/users?user=...&domain=...&password=...&name=...&email=...
739 if (method == "POST" && path.size() == 1)
740 {
741 if (!hasParam(query, "user") || !hasParam(query, "domain"))
742 {
743 sendError(pageNumber, 400, "Missing required parameter: user and/or domain");
744 return;
745 }
746 Data user = param(query, "user");
747 Data domain = param(query, "domain");
748 Data password = param(query, "password");
749 Data name = param(query, "name");
750 Data email = param(query, "email");
751
752 // Matches HTML form: realm is set to domain
753 if (userStore.addUser(user, domain, domain, password, true, name, email))
754 {
755 json::Object obj;
756 obj["user"] = json::String(user.c_str());
757 obj["domain"] = json::String(domain.c_str());
758 obj["id"] = json::String((user + Data("@") + domain).c_str());
759 sendJson(pageNumber, 200, successEnvelope(obj));
760 }
761 else
762 {
763 sendError(pageNumber, 500, "Failed to add user (database error; possible duplicate)");
764 }
765 return;
766 }
767
768 // All remaining operations need {user@domain} in path[1]
769 if (path.size() < 2)
770 {
771 sendMethodNotAllowed(pageNumber, method);
772 return;
773 }
774
775 // Parse {user@domain}
776 const Data& id = path[1];
777 Data userPart;
778 Data domainPart;
779 {
780 Data::size_type at = id.find("@");
781 if (at == Data::npos)
782 {
783 sendError(pageNumber, 400, "User identifier must be user@domain");
784 return;
785 }
786 userPart = id.substr(0, at);
787 domainPart = id.substr(at + 1);
788 }
789
790 // The key uses realm; HTML code uses domain as realm.
791 UserStore::Key key = userStore.buildKey(userPart, domainPart);
792
793 if (method == "GET")
794 {
795 AbstractDb::UserRecord rec = userStore.getUserInfo(key);
796 if (rec.user.empty())
797 {
798 sendNotFound(pageNumber);
799 return;
800 }
801 json::Object obj = userToJson(rec);
802 obj["id"] = json::String((rec.user + Data("@") + rec.domain).c_str());
803 sendJson(pageNumber, 200, successEnvelope(obj));
804 return;
805 }
806
807 if (method == "PUT")
808 {
809 AbstractDb::UserRecord rec = userStore.getUserInfo(key);
810 if (rec.user.empty())
811 {
812 sendNotFound(pageNumber);
813 return;
814 }
815
816 // Partial-update semantics: any field not supplied stays unchanged.
817 // Note that the stored password hash is MD5(user:realm:password), so
818 // if the username or domain changes, the old hash is no longer valid
819 // for the new identity. In that case a new password is mandatory.
820 Data newUser = hasParam(query, "user") ? param(query, "user") : rec.user;
821 Data newDomain = hasParam(query, "domain") ? param(query, "domain") : rec.domain;
822 Data newName = hasParam(query, "name") ? param(query, "name") : rec.name;
823 Data newEmail = hasParam(query, "email") ? param(query, "email") : rec.email;
824
825 // Realm tracks domain (matching how addUser is called elsewhere).
826 Data newRealm = newDomain;
827
828 bool identityChanged = (newUser != rec.user) || (newDomain != rec.domain);
829 bool passwordProvided = hasParam(query, "password") && !param(query, "password").empty();
830
831 if (identityChanged && !passwordProvided)
832 {
833 sendError(pageNumber, 400,
834 "Changing user or domain requires a new password "
835 "(the stored password hash is bound to user+realm)");
836 return;
837 }
838
839 Data password;
840 Data passwordHashAlt;
841 bool applyA1HashToPassword;
842 if (passwordProvided)
843 {
844 password = param(query, "password");
845 applyA1HashToPassword = true;
846 }
847 else
848 {
849 // Identity is unchanged and no new password supplied: keep existing hash.
850 password = rec.passwordHash;
851 passwordHashAlt = rec.passwordHashAlt;
852 applyA1HashToPassword = false;
853 }
854
855 // updateUser handles a change of key internally (adds new, deletes old).
856 if (userStore.updateUser(key, newUser, newDomain, newRealm, password,
857 applyA1HashToPassword, newName, newEmail,
858 passwordHashAlt))
859 {
860 // If identity changed, the record now lives under a different key.
861 UserStore::Key updatedKey = identityChanged
862 ? userStore.buildKey(newUser, newRealm)
863 : key;
864 AbstractDb::UserRecord updated = userStore.getUserInfo(updatedKey);
865 json::Object obj = userToJson(updated);
866 obj["id"] = json::String((updated.user + Data("@") + updated.domain).c_str());
867 sendJson(pageNumber, 200, successEnvelope(obj));
868 }
869 else
870 {
871 sendError(pageNumber, 500, "Failed to update user (database error)");
872 }
873 return;
874 }
875
876 if (method == "DELETE")
877 {
878 AbstractDb::UserRecord rec = userStore.getUserInfo(key);
879 if (rec.user.empty())
880 {
881 sendNotFound(pageNumber);
882 return;
883 }
884 userStore.eraseUser(key);
885 sendOk(pageNumber);
886 return;
887 }
888
889 sendMethodNotAllowed(pageNumber, method);
890}
891
892// ---------------------------------------------------------------------------
893// ACLs
894// ---------------------------------------------------------------------------
895void
896RestAdmin::handleAcls(const Data& method,
897 const std::vector<Data>& path,
898 const ParamMap& query,
899 int pageNumber)
900{
901 AclStore& aclStore = mWebAdmin.mStore.mAclStore;
902
903 if (method == "GET" && path.size() == 1)
904 {
905 json::Array arr;
906
907 // TLS peer name ACLs
908 AclStore::Key key = aclStore.getFirstTlsPeerNameKey();
909 while (key != Data::Empty)
910 {
911 json::Object obj;
912 obj["key"] = json::String(key.c_str());
913 obj["type"] = json::String("tlsPeerName");
914 obj["tlsPeerName"] = json::String(aclStore.getTlsPeerName(key).c_str());
915 arr.Insert(obj);
916 key = aclStore.getNextTlsPeerNameKey(key);
917 }
918
919 // Address ACLs
920 key = aclStore.getFirstAddressKey();
921 while (key != Data::Empty)
922 {
923 json::Object obj;
924 obj["key"] = json::String(key.c_str());
925 obj["type"] = json::String("address");
926 obj["address"] = json::String(aclStore.getAddressTuple(key).presentationFormat().c_str());
927 obj["mask"] = json::Number(aclStore.getAddressMask(key));
928 obj["port"] = json::Number(aclStore.getAddressTuple(key).getPort());
929 obj["transport"] = json::String(Tuple::toData(aclStore.getAddressTuple(key).getType()).c_str());
930 arr.Insert(obj);
931 key = aclStore.getNextAddressKey(key);
932 }
933
934 sendJson(pageNumber, 200, successEnvelope(arr));
935 return;
936 }
937
938 if (method == "POST" && path.size() == 1)
939 {
940 if (!hasParam(query, "hostOrIp"))
941 {
942 sendError(pageNumber, 400, "Missing required parameter: hostOrIp");
943 return;
944 }
945 Data hostOrIp = param(query, "hostOrIp");
946 int port = param(query, "port", "0").convertInt();
947 TransportType transport = Tuple::toTransport(param(query, "transport", "UDP"));
948
949 if (aclStore.addAcl(hostOrIp, port, transport))
950 {
951 json::Object obj;
952 obj["hostOrIp"] = json::String(hostOrIp.c_str());
953 obj["port"] = json::Number(port);
954 obj["transport"] = json::String(param(query, "transport", "UDP").c_str());
955 sendJson(pageNumber, 200, successEnvelope(obj));
956 }
957 else
958 {
959 sendError(pageNumber, 400, Data("Failed to add ACL (parse error or database error): ") + hostOrIp);
960 }
961 return;
962 }
963
964 if (method == "DELETE" && path.size() == 2)
965 {
966 const Data& key = path[1];
967 aclStore.eraseAcl(key);
968 sendOk(pageNumber);
969 return;
970 }
971
972 sendMethodNotAllowed(pageNumber, method);
973}
974
975// ---------------------------------------------------------------------------
976// Routes
977// ---------------------------------------------------------------------------
978void
979RestAdmin::handleRoutes(const Data& method,
980 const std::vector<Data>& path,
981 const ParamMap& query,
982 int pageNumber)
983{
984 RouteStore& routeStore = mWebAdmin.mStore.mRouteStore;
985
986 auto routeToJson = [](const AbstractDb::RouteRecord& rec, const Data& key) -> json::Object {
987 json::Object obj;
988 obj["key"] = json::String(key.c_str());
989 obj["uri"] = json::String(rec.mMatchingPattern.c_str());
990 obj["method"] = json::String(rec.mMethod.c_str());
991 obj["event"] = json::String(rec.mEvent.c_str());
992 obj["destination"] = json::String(rec.mRewriteExpression.c_str());
993 obj["order"] = json::Number(rec.mOrder);
994 return obj;
995 };
996
997 if (method == "GET" && path.size() == 1)
998 {
999 json::Array arr;
1000 for (RouteStore::Key key = routeStore.getFirstKey();
1001 !key.empty();
1002 key = routeStore.getNextKey(key))
1003 {
1004 AbstractDb::RouteRecord rec = routeStore.getRouteRecord(key);
1005 arr.Insert(routeToJson(rec, key));
1006 }
1007 sendJson(pageNumber, 200, successEnvelope(arr));
1008 return;
1009 }
1010
1011 if (method == "POST" && path.size() == 1)
1012 {
1013 if (!hasParam(query, "uri") || !hasParam(query, "destination"))
1014 {
1015 sendError(pageNumber, 400, "Missing required parameter: uri and/or destination");
1016 return;
1017 }
1018 Data uri = param(query, "uri");
1019 Data destination = param(query, "destination");
1020 Data rmethod = param(query, "method");
1021 Data event = param(query, "event");
1022 int order = param(query, "order", "0").convertInt();
1023
1024 if (routeStore.addRoute(rmethod, event, uri, destination, order))
1025 {
1026 json::Object obj;
1027 obj["uri"] = json::String(uri.c_str());
1028 obj["destination"] = json::String(destination.c_str());
1029 obj["method"] = json::String(rmethod.c_str());
1030 obj["event"] = json::String(event.c_str());
1031 obj["order"] = json::Number(order);
1032 sendJson(pageNumber, 200, successEnvelope(obj));
1033 }
1034 else
1035 {
1036 sendError(pageNumber, 500, "Failed to add route (duplicate or database error)");
1037 }
1038 return;
1039 }
1040
1041 if (path.size() < 2)
1042 {
1043 sendMethodNotAllowed(pageNumber, method);
1044 return;
1045 }
1046
1047 const Data& key = path[1];
1048
1049 if (method == "PUT")
1050 {
1051 AbstractDb::RouteRecord rec = routeStore.getRouteRecord(key);
1052 // We don't have a reliable way to detect "not found" from getRouteRecord
1053 // (it returns a default-constructed record). A record with all fields
1054 // empty is treated as not found.
1055 if (rec.mMatchingPattern.empty() && rec.mRewriteExpression.empty() &&
1056 rec.mMethod.empty() && rec.mEvent.empty())
1057 {
1058 sendNotFound(pageNumber);
1059 return;
1060 }
1061
1062 // Partial update
1063 Data uri = hasParam(query, "uri") ? param(query, "uri") : rec.mMatchingPattern;
1064 Data destination = hasParam(query, "destination") ? param(query, "destination") : rec.mRewriteExpression;
1065 Data rmethod = hasParam(query, "method") ? param(query, "method") : rec.mMethod;
1066 Data event = hasParam(query, "event") ? param(query, "event") : rec.mEvent;
1067 int order = hasParam(query, "order") ? param(query, "order").convertInt() : rec.mOrder;
1068
1069 if (uri.empty() || destination.empty())
1070 {
1071 sendError(pageNumber, 400, "Route uri and destination must not be empty");
1072 return;
1073 }
1074
1075 if (routeStore.updateRoute(key, rmethod, event, uri, destination, order))
1076 {
1077 AbstractDb::RouteRecord updated = routeStore.getRouteRecord(key);
1078 sendJson(pageNumber, 200, successEnvelope(routeToJson(updated, key)));
1079 }
1080 else
1081 {
1082 sendError(pageNumber, 500, "Failed to update route (database error)");
1083 }
1084 return;
1085 }
1086
1087 if (method == "DELETE")
1088 {
1089 routeStore.eraseRoute(key);
1090 sendOk(pageNumber);
1091 return;
1092 }
1093
1094 sendMethodNotAllowed(pageNumber, method);
1095}
1096
1097// ---------------------------------------------------------------------------
1098// Filters (GET only)
1099// ---------------------------------------------------------------------------
1100void
1101RestAdmin::handleFilters(const Data& method,
1102 const std::vector<Data>& path,
1103 const ParamMap& /*query*/,
1104 int pageNumber)
1105{
1106 FilterStore& filterStore = mWebAdmin.mStore.mFilterStore;
1107
1108 if (method == "GET" && path.size() == 1)
1109 {
1110 json::Array arr;
1111 for (FilterStore::Key key = filterStore.getFirstKey();
1112 !key.empty();
1113 key = filterStore.getNextKey(key))
1114 {
1115 AbstractDb::FilterRecord rec = filterStore.getFilterRecord(key);
1116 Data actionStr("Accept");
1117 if (rec.mAction == FilterStore::Reject) actionStr = "Reject";
1118 else if (rec.mAction == FilterStore::SQLQuery) actionStr = "SQLQuery";
1119
1120 json::Object obj;
1121 obj["key"] = json::String(key.c_str());
1122 obj["cond1Header"] = json::String(rec.mCondition1Header.c_str());
1123 obj["cond1Regex"] = json::String(rec.mCondition1Regex.c_str());
1124 obj["cond2Header"] = json::String(rec.mCondition2Header.c_str());
1125 obj["cond2Regex"] = json::String(rec.mCondition2Regex.c_str());
1126 obj["method"] = json::String(rec.mMethod.c_str());
1127 obj["event"] = json::String(rec.mEvent.c_str());
1128 obj["action"] = json::String(actionStr.c_str());
1129 obj["actionData"] = json::String(rec.mActionData.c_str());
1130 obj["order"] = json::Number(rec.mOrder);
1131 arr.Insert(obj);
1132 }
1133 sendJson(pageNumber, 200, successEnvelope(arr));
1134 return;
1135 }
1136
1137 sendMethodNotAllowed(pageNumber, method);
1138}
1139
1140// ---------------------------------------------------------------------------
1141// Settings / stack info / stack statistics / congestion
1142// ---------------------------------------------------------------------------
1143void
1144RestAdmin::handleSettings(const Data& method, int pageNumber)
1145{
1146 if (method != "GET")
1147 {
1148 sendMethodNotAllowed(pageNumber, method);
1149 return;
1150 }
1151
1152 Data buffer;
1153 {
1154 DataStream strm(buffer);
1155 strm << mWebAdmin.mProxy.getConfig();
1156 strm.flush();
1157 }
1158 sendJson(pageNumber, 200, successEnvelope(json::String(buffer.c_str())));
1159}
1160
1161void
1162RestAdmin::handleStackInfo(const Data& method, int pageNumber)
1163{
1164 if (method != "GET")
1165 {
1166 sendMethodNotAllowed(pageNumber, method);
1167 return;
1168 }
1169
1170 Data buffer;
1171 {
1172 DataStream strm(buffer);
1173 mWebAdmin.mProxy.getStack().dump(strm);
1174 strm.flush();
1175 }
1176 sendJson(pageNumber, 200, successEnvelope(json::String(buffer.c_str())));
1177}
1178
1179void
1180RestAdmin::handleStats(const Data& method,
1181 const std::vector<Data>& path,
1182 int pageNumber)
1183{
1184 // POST /api/v1/stats/reset -> zero out the stack's statistics counters.
1185 if (method == "POST" && path.size() == 2 && path[1] == "reset")
1186 {
1187 mWebAdmin.mProxy.getStack().zeroOutStatistics();
1188 sendOk(pageNumber);
1189 return;
1190 }
1191
1192 // GET /api/v1/stats -> fresh stack statistics from the StatisticsManager.
1193 // This is async: pollStatistics() asks the stack to deliver a message, and
1194 // we wait (with a timeout) for ReproRunner to route that message to
1195 // WebAdmin::handleStatisticsMessage, which populates mStatsPayload.
1196 if (method == "GET" && path.size() == 1)
1197 {
1199 bool gotPayload = false;
1200
1201 {
1202 Lock lock(mWebAdmin.mStatsMutex);
1203
1204 // Clear any previous payload so we wait for *this* request's poll.
1205 mWebAdmin.mStatsReady = false;
1206
1207 if (!mWebAdmin.mProxy.getStack().pollStatistics())
1208 {
1209 sendError(pageNumber, 503,
1210 "Statistics Manager is not enabled "
1211 "(set StatisticsLogInterval in repro.config)");
1212 return;
1213 }
1214
1215 // Wait up to 10 seconds for the stats message to arrive. Loop to
1216 // handle spurious wakeups; exit when either mStatsReady becomes
1217 // true or the deadline passes.
1218 const std::chrono::steady_clock::time_point deadline =
1219 std::chrono::steady_clock::now() + std::chrono::seconds(10);
1220 while (!mWebAdmin.mStatsReady)
1221 {
1222 std::chrono::steady_clock::duration remaining =
1223 deadline - std::chrono::steady_clock::now();
1224 if (remaining <= std::chrono::steady_clock::duration::zero())
1225 {
1226 break;
1227 }
1228 mWebAdmin.mStatsCondition.wait_for(lock, remaining);
1229 }
1230
1231 if (mWebAdmin.mStatsReady)
1232 {
1233 // Copy the payload under the lock, then serialize to JSON
1234 // outside the critical section.
1235 payload = mWebAdmin.mStatsPayload;
1236 gotPayload = true;
1237 }
1238 }
1239
1240 if (!gotPayload)
1241 {
1242 sendError(pageNumber, 504,
1243 "Timed out waiting for stack statistics");
1244 return;
1245 }
1246
1247 sendJson(pageNumber, 200, successEnvelope(payloadToJson(payload)));
1248 return;
1249 }
1250
1251 sendMethodNotAllowed(pageNumber, method);
1252}
1253
1254void
1255RestAdmin::handleCongestion(const Data& method, int pageNumber)
1256{
1257 if (method != "GET")
1258 {
1259 sendMethodNotAllowed(pageNumber, method);
1260 return;
1261 }
1262
1263 if (!mWebAdmin.mProxy.getStack().getCongestionManager())
1264 {
1265 sendJson(pageNumber, 200, successEnvelope(json::Null()));
1266 return;
1267 }
1268
1269 Data buffer;
1270 {
1271 DataStream strm(buffer);
1272 mWebAdmin.mProxy.getStack().getCongestionManager()->encodeCurrentState(strm);
1273 strm.flush();
1274 }
1275 sendJson(pageNumber, 200, successEnvelope(json::String(buffer.c_str())));
1276}
1277
1278// ---------------------------------------------------------------------------
1279// Log level
1280// ---------------------------------------------------------------------------
1281void
1282RestAdmin::handleLogLevel(const Data& method,
1283 const ParamMap& query,
1284 int pageNumber)
1285{
1286 // Log::toString returns values like "LOG_INFO"; we strip the LOG_ prefix
1287 // so the API uses the same short names ("INFO", "DEBUG", etc.) that
1288 // Log::toLevel accepts as input.
1289 auto levelName = [](Log::Level l) -> Data {
1290 Data s = Log::toString(l);
1291 const Data prefix("LOG_");
1292 if (s.prefix(prefix))
1293 {
1294 return s.substr(prefix.size());
1295 }
1296 return s;
1297 };
1298
1299 if (method == "GET")
1300 {
1301 json::Object obj;
1302 obj["level"] = json::String(levelName(Log::level()).c_str());
1303 sendJson(pageNumber, 200, successEnvelope(obj));
1304 return;
1305 }
1306
1307 if (method == "PUT")
1308 {
1309 if (!hasParam(query, "level"))
1310 {
1311 sendError(pageNumber, 400, "Missing required parameter: level");
1312 return;
1313 }
1314 Data levelStr = param(query, "level");
1315 Log::Level l = Log::toLevel(levelStr);
1316 Log::setLevel(l);
1317 InfoLog(<< "Log level changed via REST API to: " << levelStr);
1318
1319 json::Object obj;
1320 obj["level"] = json::String(levelName(Log::level()).c_str());
1321 sendJson(pageNumber, 200, successEnvelope(obj));
1322 return;
1323 }
1324
1325 sendMethodNotAllowed(pageNumber, method);
1326}
1327
1328// ---------------------------------------------------------------------------
1329// DNS cache
1330// ---------------------------------------------------------------------------
1331void
1332RestAdmin::handleDnsCache(const Data& method,
1333 const std::vector<Data>& path,
1334 int pageNumber)
1335{
1336 // GET /api/v1/dnscache -> retrieve cache dump
1337 if (method == "GET" && path.size() == 1)
1338 {
1339 Data cacheDump;
1340 {
1341 Lock lock(mWebAdmin.mDnsCacheMutex);
1342 mWebAdmin.mProxy.getStack().getDnsCacheDump(make_pair(0, 0), &mWebAdmin);
1343 // Blocks until onDnsCacheDumpRetrieved populates mDnsCache and
1344 // signals the condition.
1345 mWebAdmin.mDnsCacheCondition.wait(lock);
1346 cacheDump = mWebAdmin.mDnsCache;
1347 }
1348 sendJson(pageNumber, 200, successEnvelope(json::String(cacheDump.c_str())));
1349 return;
1350 }
1351
1352 // DELETE /api/v1/dnscache -> clear
1353 if (method == "DELETE" && path.size() == 1)
1354 {
1355 mWebAdmin.mProxy.getStack().clearDnsCache();
1356 sendOk(pageNumber);
1357 return;
1358 }
1359
1360 // POST /api/v1/dnscache/reload -> reload DNS servers (reread resolv.conf etc.)
1361 if (method == "POST" && path.size() == 2 && path[1] == "reload")
1362 {
1363 mWebAdmin.mProxy.getStack().reloadDnsServers();
1364 sendOk(pageNumber);
1365 return;
1366 }
1367
1368 sendMethodNotAllowed(pageNumber, method);
1369}
1370
1371// ---------------------------------------------------------------------------
1372// Restart
1373// ---------------------------------------------------------------------------
1374void
1375RestAdmin::handleRestart(const Data& method, int pageNumber)
1376{
1377 if (method != "POST")
1378 {
1379 sendMethodNotAllowed(pageNumber, method);
1380 return;
1381 }
1382
1383 unsigned short port = mWebAdmin.mProxy.getConfig().getConfigUnsignedShort("CommandPort", 0);
1384 if (port == 0)
1385 {
1386 sendError(pageNumber, 503, "CommandServer must be running (CommandPort must be set) to use restart");
1387 return;
1388 }
1389
1390 // Connect to local CommandPort and send the restart XML, same mechanism
1391 // as WebAdmin::buildRestartSubPage.
1392 const char* host = "127.0.0.1";
1393 struct addrinfo hints = {};
1394 struct addrinfo* res = nullptr;
1395 hints.ai_family = AF_INET;
1396 hints.ai_socktype = SOCK_STREAM;
1397
1398 if (getaddrinfo(host, nullptr, &hints, &res) != 0 || res == nullptr)
1399 {
1400 sendError(pageNumber, 500, "Failed to resolve loopback for restart");
1401 return;
1402 }
1403
1404 struct sockaddr_in servAddr = {};
1405 memcpy(&servAddr, res->ai_addr, res->ai_addrlen);
1406 servAddr.sin_port = htons(port);
1407 freeaddrinfo(res);
1408
1409 int sd = (int)socket(AF_INET, SOCK_STREAM, 0);
1410 if (sd < 0)
1411 {
1412 sendError(pageNumber, 500, "Failed to create socket for restart");
1413 return;
1414 }
1415
1416 struct sockaddr_in localAddr = {};
1417 localAddr.sin_family = AF_INET;
1418 localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
1419 localAddr.sin_port = 0;
1420 if (::bind(sd, (struct sockaddr*)&localAddr, sizeof(localAddr)) < 0)
1421 {
1422 closeSocket(sd);
1423 sendError(pageNumber, 500, "Failed to bind local socket for restart");
1424 return;
1425 }
1426
1427 if (::connect(sd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0)
1428 {
1429 closeSocket(sd);
1430 sendError(pageNumber, 500, "Failed to connect to CommandPort for restart");
1431 return;
1432 }
1433
1434 Data request("<Restart>\r\n <Request>\r\n </Request>\r\n</Restart>\r\n");
1435 if (send(sd, request.c_str(), (int)request.size(), 0) < 0)
1436 {
1437 closeSocket(sd);
1438 sendError(pageNumber, 500, "Failed to send restart command");
1439 return;
1440 }
1441
1442 closeSocket(sd);
1443
1444 json::Object obj;
1445 obj["status"] = json::String("restarting");
1446 sendJson(pageNumber, 200, successEnvelope(obj));
1447}
1448
1449// ---------------------------------------------------------------------------
1450// Certs reload
1451// ---------------------------------------------------------------------------
1452void
1453RestAdmin::handleCertsReload(const Data& method, int pageNumber)
1454{
1455 if (method != "POST")
1456 {
1457 sendMethodNotAllowed(pageNumber, method);
1458 return;
1459 }
1460
1461#ifdef USE_SSL
1462 mWebAdmin.mProxy.getStack().reloadCertificates();
1463 sendOk(pageNumber);
1464#else
1465 sendError(pageNumber, 501, "Certificates not supported (built without SSL)");
1466#endif
1467}
1468
1469
1470/* ====================================================================
1471
1472 Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com
1473 All rights reserved.
1474
1475 Redistribution and use in source and binary forms, with or without
1476 modification, are permitted provided that the following conditions are
1477 met:
1478
1479 1. Redistributions of source code must retain the above copyright
1480 notice, this list of conditions and the following disclaimer.
1481
1482 2. Redistributions in binary form must reproduce the above copyright
1483 notice, this list of conditions and the following disclaimer in the
1484 documentation and/or other materials provided with the distribution.
1485
1486 3. Neither the name of Plantronics nor the names of its contributors
1487 may be used to endorse or promote products derived from this
1488 software without specific prior written permission.
1489
1490 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1491 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1492 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1493 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1494 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
1495 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
1496 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
1497 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
1498 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
1499 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
1500 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1501
1502 ==================================================================== */
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define WarningLog(args_)
Definition Logger.hxx:134
Handles cross-platform sockets compatibility.
void eraseAcl(const resip::Data &tlsPeerName, const resip::Data &address, const short &mask, const short &port, const short &family, const short &transport)
Definition AclStore.cxx:289
resip::Data getTlsPeerName(const resip::Data &key)
Definition AclStore.cxx:461
short getAddressMask(const resip::Data &key)
Definition AclStore.cxx:485
Key getNextTlsPeerNameKey(Key &key)
Definition AclStore.cxx:381
Key getFirstTlsPeerNameKey()
Definition AclStore.cxx:341
Key getNextAddressKey(Key &key)
Definition AclStore.cxx:441
resip::Tuple getAddressTuple(const resip::Data &key)
Definition AclStore.cxx:473
Key getFirstAddressKey()
Definition AclStore.cxx:401
bool addAcl(const resip::Data &tlsPeerName, const resip::Data &address, const short &mask, const short &port, const short &family, const short &transport)
Definition AclStore.cxx:62
bool addDomain(const resip::Data &domain, const int tlsPort)
std::map< resip::Data, AbstractDb::ConfigRecord > ConfigData
void eraseDomain(const resip::Data &domain)
const ConfigData & getConfigs() const
Key getNextKey(Key &key)
AbstractDb::FilterRecord getFilterRecord(const resip::Data &key)
ProxyConfig & getConfig() noexcept
Definition Proxy.hxx:89
resip::SipStack & getStack() noexcept
Definition Proxy.hxx:88
RestAdmin(WebAdmin &webAdmin)
void dispatch(const resip::Data &method, const resip::Data &uri, int pageNumber, const resip::Data &authenticatedUser)
bool addRoute(const resip::Data &method, const resip::Data &event, const resip::Data &matchingPattern, const resip::Data &rewriteExpression, const short order)
AbstractDb::RouteRecord getRouteRecord(const resip::Data &key)
Key getNextKey(Key &key)
void eraseRoute(const resip::Data &method, const resip::Data &event, const resip::Data &matchingPattern, const short order)
bool updateRoute(const resip::Data &originalKey, const resip::Data &method, const resip::Data &event, const resip::Data &matchingPattern, const resip::Data &rewriteExpression, const short order)
void eraseStaticReg(const resip::Uri &aor, const resip::NameAddr &contact)
FilterStore mFilterStore
Definition Store.hxx:40
AclStore mAclStore
Definition Store.hxx:37
ConfigStore mConfigStore
Definition Store.hxx:38
StaticRegStore mStaticRegStore
Definition Store.hxx:39
RouteStore mRouteStore
Definition Store.hxx:36
UserStore mUserStore
Definition Store.hxx:34
static Key buildKey(const resip::Data &user, const resip::Data &domain)
bool addUser(const resip::Data &user, const resip::Data &domain, const resip::Data &realm, const resip::Data &password, bool applyA1HashToPassword, const resip::Data &fullName, const resip::Data &emailAddress, const resip::Data &passwordHashAlt=resip::Data::Empty)
Definition UserStore.cxx:50
void eraseUser(const Key &key)
Definition UserStore.cxx:86
bool updateUser(const Key &originalKey, const resip::Data &user, const resip::Data &domain, const resip::Data &realm, const resip::Data &password, bool applyA1HashToPassword, const resip::Data &fullName, const resip::Data &emailAddress, const resip::Data &passwordHashAlt=resip::Data::Empty)
Definition UserStore.cxx:92
AbstractDb::UserRecord getUserInfo(const Key &key) const
Definition UserStore.cxx:36
void setApiResponse(int pageNumber, int statusCode, const resip::Data &jsonBody)
The abstract base-class for all exceptions thrown by resip code.
unsigned short getConfigUnsignedShort(const resip::Data &name, int defaultValue) const
virtual EncodeStream & encodeCurrentState(EncodeStream &strm) const =0
Get the current state of all monitored fifos.to a stream.
A single contact record, bound to an Aor during registration.
An iostream that operates on an existing Data.
An alternative to std::string, encapsulates an arbitrary buffer of bytes.
Definition Data.hxx:90
static const Data Empty
Constant that represents a zero-length data.
Definition Data.hxx:882
const char * c_str() const
Returns a null-terminated string representing.
Definition Data.cxx:1097
bool prefix(const Data &pre) const noexcept
Returns true if this Data starts with the bytes indicated by the passed-in Data.
Definition Data.cxx:1888
Data substr(size_type first, size_type count=Data::npos) const
Copies a portion of this Data into a new Data.
Definition Data.cxx:1932
bool empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
Data urlDecoded() const
Performs in-place HTTP URL un-escaping of a Data.
Definition Data.cxx:1341
size_t size_type
Definition Data.hxx:94
size_type size() const noexcept
Returns the number of bytes in this Data.
Definition Data.hxx:599
static Data from(const T &x)
Converts from arbitrary other type to Data.
Definition Data.hxx:398
size_type find(const Data &match, size_type start=0) const
Finds a specified sequence of bytes in this Data.
Definition Data.cxx:1947
static const size_type npos
Represents an impossible position; returned to indicate failure to find.
Definition Data.hxx:887
int convertInt() const
Takes the contents of the Data and converts them to an integer.
Definition Data.cxx:1684
const char * data() const noexcept
Returns a pointer to the contents of this Data.
Definition Data.hxx:608
SIP body type for holding PIDF contents (MIME content-type application/pidf+xml).
static Data toString(Level l)
Definition Log.cxx:574
static Level toLevel(const Data &l)
Definition Log.cxx:580
static void setLevel(Level level)
Set logging level for current thread.
Definition Log.cxx:423
static Level level()
Return logging level for current thread.
Definition Log.hxx:221
bool exists(const Param< NameAddr > &paramType) const
Definition NameAddr.cxx:477
const Data & param(const ExtensionParameter &param) const
Const accessor for non-natively-supported parameter types.
Provides string-parsing functionality with protection from buffer overflow.
iterator end()
Returns an iterator pointing to the last header field value.
iterator begin()
Returns an iterator pointing to the first header field value.
Abstract interface of a datastore of all publication documents processed by DUM.
std::map< resip::Data, ETagToDocumentMap > KeyToETagMap
virtual KeyToETagMap & getDocuments()=0
Abstract interface of a datastore of all registered endpoints processed by DUM.
virtual void removeContact(const Uri &aor, const ContactInstanceRecord &rec)=0
virtual void getContacts(const Uri &aor, ContactList &container)=0
virtual void getAors(UriList &container)=0
EncodeStream & dump(EncodeStream &strm) const
output current state of the stack - for debug
void clearDnsCache()
Clear the DNS Cache.
void getDnsCacheDump(std::pair< unsigned long, unsigned long > key, GetDnsCacheDumpHandler *handler)
Get a string representation of the DNS Cache.
void reloadCertificates()
reload the TLS X.509 certificates
Definition SipStack.cxx:323
void zeroOutStatistics()
Resets all of the cumulative statistics counters.
bool pollStatistics()
Immediately polls for statistics to be logged and sent to external handlers, instead of waiting for n...
void reloadDnsServers()
Check if DnsServers list has changed, and if so reinitializes the DNS resolver (ares).
static uint64_t getTimeSecs()
Returns the current clock time in seconds.
Definition Timer.hxx:78
Data presentationFormat() const
Wrapper around the inet_top() method.
Definition Tuple.cxx:426
static const Data & toData(TransportType)
Converts the TransportType to a string representation of the transport type, e.g.
Definition Tuple.cxx:927
int getPort() const
Definition Tuple.cxx:466
TransportType getType() const
Definition Tuple.hxx:122
static TransportType toTransport(const Data &)
Converts a string representation of transport type, i.e.
Definition Tuple.cxx:921
Represents the "SIP-URI" and "SIPS-URI" elements in the RFC 3261 grammar.
Definition Uri.hxx:22
CongestionManager * getCongestionManager()
Accessor for the CongestionManager object the stack is using.
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
static const uint64_t NeverExpire
std::unique_lock< Mutex > Lock
A convenience class to lock a Lockable (such as a Mutex) on object creation, and unlock on destructio...
Definition Lock.hxx:28
int closeSocket(Socket fd)
Definition Socket.cxx:134
TransportType
An enumeration of transport protocols.
std::list< ContactInstanceRecord > ContactList
unsigned __int64 uint64_t
Definition stdint.h:95
unsigned int responsesSentByMethod[MAX_METHODS]
unsigned int responsesSentByMethodByCode[MAX_METHODS][MaxCode]
unsigned int requestsSentByMethod[MAX_METHODS]
unsigned int responsesRetransmittedByMethod[MAX_METHODS]
unsigned int responsesReceivedByMethodByCode[MAX_METHODS][MaxCode]
unsigned int requestsRetransmittedByMethod[MAX_METHODS]
unsigned int responsesRetransmittedByMethodByCode[MAX_METHODS][MaxCode]
unsigned int requestsReceivedByMethod[MAX_METHODS]
unsigned int responsesReceivedByMethod[MAX_METHODS]