reSIProcate
SIP stack, Dialog Usage Manager and related libraries
Loading...
Searching...
No Matches
MessageSilo.cxx
Go to the documentation of this file.
1#if defined(HAVE_CONFIG_H)
2#include "config.h"
3#endif
4
11#include "repro/Proxy.hxx"
14#include "rutil/Logger.hxx"
15
17
18#include <utility>
19
20#define RESIPROCATE_SUBSYSTEM resip::Subsystem::REPRO
21
22using namespace resip;
23using namespace repro;
24using namespace std;
25
26#define SILO_CLEANUP_PERIOD 86400 // look for expired records at most every 24 hours (86400 seconds)
27
29{
30public:
32 const resip::Data& tid,
33 resip::TransactionUser* passedtu):
34 AsyncProcessorMessage(proc, tid, passedtu)
35 {
36 }
37
38 virtual EncodeStream& encode(EncodeStream& strm) const { strm << "AsyncAddToSiloMessage(tid=" << mTid << ", aor=" << mDestUri << ")"; return strm; }
39
45};
46
48{
49public:
51 const resip::Data& tid,
52 resip::TransactionUser* passedtu):
53 AsyncProcessorMessage(proc, tid, passedtu)
54 {
55 }
56
57 virtual EncodeStream& encode(EncodeStream& strm) const { strm << "AsyncDrainSiloMessage(aor=" << mAor << ")"; return strm; }
58
61};
62
63MessageSilo::MessageSilo(ProxyConfig& config, Dispatcher* asyncDispatcher) :
64 AsyncProcessor("MessageSilo", asyncDispatcher),
65 mSiloStore(config.getDataStore()->mSiloStore),
66 mDestFilterRegex(0),
67 mMimeTypeFilterRegex(0),
68 mExpirationTime(config.getConfigUnsignedLong("MessageSiloExpirationTime", 2592000 /* 30 days */)),
69 mAddDateHeader(config.getConfigBool("MessageSiloAddDateHeader", true)),
70 mMaxContentLength(config.getConfigUnsignedLong("MessageSiloMaxContentLength", 4096)),
71 mSuccessStatusCode(config.getConfigUnsignedShort("MessageSiloSuccessStatusCode", 202)),
72 mFilteredMimeTypeStatusCode(config.getConfigUnsignedShort("MessageSiloFilteredMimeTypeStatusCode", 200)),
73 mFailureStatusCode(config.getConfigUnsignedShort("MessageSiloFailureStatusCode", 480)),
74 mLastSiloCleanupTime(time(0)) // set to now
75{
76 Data destFilterRegex = config.getConfigData("MessageSiloDestFilterRegex", "", false);
77 Data mimeTypeFilterRegex = config.getConfigData("MessageSiloMimeTypeFilterRegex", "application\\/im\\-iscomposing\\+xml", false);
78 if(!destFilterRegex.empty())
79 {
80 try
81 {
82 // Note: this code originally used the PCRE (Perl Compatible) regular expression library. The ECMAScript standard is a subset
83 // of the Perl regular expression syntax. Posix regular expression syntax is quite a bit different (ie: std::regex_contacts::basic or
84 // std::regex_contacts::extended). To be as backwards compatible with existing regular expressions as possible, we want to
85 // use the EMCAScript syntax.
86 mDestFilterRegex = new std::regex(destFilterRegex.c_str(), std::regex_constants::ECMAScript | std::regex_constants::nosubs);
87 }
88 catch (std::regex_error&)
89 {
90 delete mDestFilterRegex;
91 ErrLog( << "MessageSilo has invalid destination filter regular expression: " << destFilterRegex);
92 mDestFilterRegex = 0;
93 }
94 }
95 if(!mimeTypeFilterRegex.empty())
96 {
97 try
98 {
99 // Note: this code originally used the PCRE (Perl Compatible) regular expression library. The ECMAScript standard is a subset
100 // of the Perl regular expression syntax. Posix regular expression syntax is quite a bit different (ie: std::regex_contacts::basic or
101 // std::regex_contacts::extended). To be as backwards compatible with existing regular expressions as possible, we want to
102 // use the EMCAScript syntax.
103 mMimeTypeFilterRegex= new std::regex(mimeTypeFilterRegex.c_str(), std::regex_constants::ECMAScript | std::regex_constants::nosubs);
104 }
105 catch (std::regex_error&)
106 {
107 delete mMimeTypeFilterRegex;
108 ErrLog( << "MessageSilo has invalid mime-type filter regular expression: " << mimeTypeFilterRegex);
109 mMimeTypeFilterRegex = 0;
110 }
111 }
112}
113
115{
116 // Clean up regex memory
117 if(mDestFilterRegex)
118 {
119 delete mDestFilterRegex;
120 mDestFilterRegex = 0;
121 }
122 if(mMimeTypeFilterRegex)
123 {
124 delete mMimeTypeFilterRegex;
125 mMimeTypeFilterRegex = 0;
126 }
127}
128
131{
132 DebugLog(<< "Monkey handling request: " << *this << "; reqcontext = " << context);
133 SipMessage& originalRequest = context.getOriginalRequest();
134
135 // Note: A potential enhancement could be to also silo messages that fail to route due to a
136 // 408 or 503 error. In order to do this, this processor needs to be part of the ResponseChain
137 // as well.
138
139 // Check if request is a MESSAGE request and if there were no targets found
140 if(originalRequest.method() == MESSAGE &&
141 !context.getResponseContext().hasTargets())
142 {
143 // There are no targets for this request - silo candidate
144
145 // Only need to silo if there is a message body
146 Contents* contents = originalRequest.getContents();
147 if(contents)
148 {
149 // Create async message now, so we can use it's storage and avoid some copies
150 AsyncAddToSiloMessage* async = new AsyncAddToSiloMessage(*this, context.getTransactionId(), &context.getProxy());
151 std::unique_ptr<ApplicationMessage> async_ptr(async);
152
153 // Check Max ContentLength setting
154 async->mMessageBody = contents->getBodyData();
155 if(async->mMessageBody.size() > mMaxContentLength)
156 {
157 InfoLog( << " MESSAGE not silo'd due to content-length exceeding max: " << async->mMessageBody.size());
158 SipMessage response;
159 Helper::makeResponse(response, originalRequest, mFailureStatusCode);
160 context.sendResponse(response);
161 return SkipThisChain;
162 }
163
164 // Check if message passes Mime-type filter
165 async->mMimeType = Data::from(contents->getType());
166 if (mMimeTypeFilterRegex)
167 {
168 // Note: Using regex_search instead of regex_match, so that we don't need to fully match
169 // the string, this is backwards compatible with the previous regexec PCRE implementation
170 if(std::regex_search(async->mMimeType.c_str(), *mMimeTypeFilterRegex))
171 {
172 // match
173 DebugLog( << " MESSAGE not silo'd due to Mime-Type filter: " << async->mMimeType);
174 if(mFilteredMimeTypeStatusCode == 0)
175 {
176 return Processor::Continue;
177 }
178 else
179 {
180 SipMessage response;
181 Helper::makeResponse(response, originalRequest, mFilteredMimeTypeStatusCode);
182 context.sendResponse(response);
183 return SkipThisChain;
184 }
185 }
186 }
187
188 // Check if message passes Destination filter
189 async->mDestUri = originalRequest.header(h_To).uri().getAOR(false /* addPort? */);
190 if (mDestFilterRegex)
191 {
192 // Note: Using regex_search instead of regex_match, so that we don't need to fully match
193 // the string, this is backwards compatible with the previous regexec PCRE implementation
194 if(std::regex_search(async->mDestUri.c_str(), *mDestFilterRegex))
195 {
196 // match
197 DebugLog( << " MESSAGE not silo'd due to destination filter: " << async->mDestUri);
198 return Processor::Continue;
199 }
200 }
201
202 // TODO (future) - check a max messages per user setting
203
204 NameAddr from(originalRequest.header(h_From));
205 from.remove(p_tag); // remove from tag
206 async->mSourceUri = Data::from(from);
207 time(&async->mOriginalSendTime); // Get now timestamp
208
209 // Dispatch async request to worker thread pool
210 mAsyncDispatcher->post(async_ptr);
211
212 SipMessage response;
213 InfoLog(<<"Message was Silo'd responding with a " << mSuccessStatusCode);
214 Helper::makeResponse(response, context.getOriginalRequest(), mSuccessStatusCode);
215 context.sendResponse(response);
216 return SkipThisChain;
217 }
218 }
219
220 // In all cases we continue - this is just a passive monkey that stores MESSAGES for later replay to recipient
221 return Processor::Continue;
222}
223
224bool
226{
227 // Running inside a worker thread here
228 AsyncAddToSiloMessage* addToSilo = dynamic_cast<AsyncAddToSiloMessage*>(msg);
229 if(addToSilo)
230 {
231 // Check if database cleanup period has passed, and if so run a cleanup pass through the database to remove
232 // silo'd messages that have been stored beyond the MessageSiloExpirationTime. If mExpirationTime is configured
233 // as 0, then records never expire, so no need to peform the cleanup.
234 // Note: addToSilo->mOriginalSendTime is always now - so no need to requery current time
235 // Run cleanup before adding new records to save iterating through 1 extra item
236 if(mExpirationTime > 0 && (addToSilo->mOriginalSendTime - mLastSiloCleanupTime) > SILO_CLEANUP_PERIOD)
237 {
238 mLastSiloCleanupTime = addToSilo->mOriginalSendTime; // reset stored silo cleanup time
239
240 mSiloStore.cleanupExpiredSiloRecords(addToSilo->mOriginalSendTime, mExpirationTime);
241 }
242
243 // TODO - look for addMessage failures and queue up to be attempted to be written later (ie. when db is back and live)
244 mSiloStore.addMessage(addToSilo->mDestUri, addToSilo->mSourceUri, addToSilo->mOriginalSendTime, addToSilo->getTransactionId(), addToSilo->mMimeType, addToSilo->mMessageBody);
245 return false;
246 }
247
248 AsyncDrainSiloMessage* drainSilo = dynamic_cast<AsyncDrainSiloMessage*>(msg);
249 if(drainSilo)
250 {
252 if(mSiloStore.getSiloRecords(drainSilo->mAor, recordList))
253 {
254 time_t now = time(0);
255
256 // Note: Tesing with BerkeleyDb and MySQL reveals that these databases return the records in insert order
257 // so there is no need to sort the records here.
258
259 AbstractDb::SiloRecordList::iterator siloIt = recordList.begin();
260 for(; siloIt != recordList.end(); siloIt++)
261 {
262 DebugLog(<< "DrainSilo: Dest=" << siloIt->mDestUri << ", Source=" << siloIt->mSourceUri << ", Datetime=" << Data::from(DateCategory(siloIt->mOriginalSentTime)) << ", MimeType=" << siloIt->mMimeType << ", Body=" << siloIt->mMessageBody);
263
264 // Only send if not too old
265 if((unsigned long)(now - siloIt->mOriginalSentTime) <= mExpirationTime)
266 {
267 ContactList::iterator contactIt = drainSilo->mRequestContacts.begin();
268 for(; contactIt != drainSilo->mRequestContacts.end(); contactIt++)
269 {
270 // send messages to each contact from register message - honour path
271 ContactInstanceRecord& rec = *contactIt;
272
273 // Removed contacts can be in the list, but they will be expired, don't send to them
274 if(rec.mRegExpires > (uint64_t)now)
275 {
276 std::unique_ptr<SipMessage> msg(new SipMessage);
277 RequestLine rLine(MESSAGE);
278 rLine.uri() = rec.mContact.uri();
279 msg->header(h_RequestLine) = rLine;
280 msg->header(h_To) = NameAddr(siloIt->mDestUri);
281 msg->header(h_MaxForwards).value() = 20;
282 msg->header(h_CSeq).method() = MESSAGE;
283 msg->header(h_CSeq).sequence() = 1;
284 msg->header(h_From) = NameAddr(siloIt->mSourceUri);
285 msg->header(h_From).param(p_tag) = Helper::computeTag(Helper::tagSize);
286 msg->header(h_CallId).value() = Helper::computeCallId();
287 Via via;
288 msg->header(h_Vias).push_back(via);
289
290 // add routes from registration path
291 if(!rec.mSipPath.empty())
292 {
293 msg->header(h_Routes).append(rec.mSipPath);
294 }
295
296 // Add Date Header if enabled
297 if(mAddDateHeader)
298 {
299 msg->header(h_Date) = DateCategory(siloIt->mOriginalSentTime);
300 }
301
302 if(rec.mUseFlowRouting &&
304 {
305 // .bwc. We only override the destination if we are sending to an
306 // outbound contact. If this is not an outbound contact, but the
307 // endpoint has given us a Contact with the correct ip-address and
308 // port, we might be able to find the connection they formed when they
309 // registered earlier, but that will happen down in TransportSelector.
310 msg->setDestination(rec.mReceivedFrom);
311 }
312
313 // Helper::processStrictRoute(*msg.get()); // Path headers must have ;lr so this isn't required
314
315 // Add mime body
316 HeaderFieldValue hfv(siloIt->mMessageBody.data(), siloIt->mMessageBody.size());
317 Mime type;
318 ParseBuffer pb(siloIt->mMimeType);
319 type.parse(pb);
320 PlainContents contents(hfv, type);
321 msg->setContents(&contents); // need to clone since body data isn't owned by message yet
322
323 mAsyncDispatcher->mStack->send(std::move(msg));
324 }
325 }
326 }
327
328 // Delete record from database
329 // Note: A potential feature enhancement would be to monitor the MESSAGE reponses and only remove
330 // from the database when a 200 reponses is seen. Care must be taken to avoid
331 // looping and handle scenarios when a user never uses a device capable of IM.
332 mSiloStore.deleteSiloRecord(siloIt->mOriginalSentTime, siloIt->mTid);
333 }
334 }
335 return false;
336 }
337
338 return false; // Nothing to queue to stack
339}
340
341bool
343{
344 // Dispatch request to check message silo for newly registered user
345 AsyncDrainSiloMessage* async = new AsyncDrainSiloMessage(*this, Data::Empty, 0); // tid and tu not needed since no response expected
346 async->mAor = reg.header(h_To).uri().getAOR(false /* addPort? */);
347 async->mRequestContacts = h->getRequestContacts();
348 std::unique_ptr<ApplicationMessage> async_ptr(async);
349 mAsyncDispatcher->post(async_ptr);
350 return true;
351}
352
353
354/* ====================================================================
355 * The Vovida Software License, Version 1.0
356 *
357 * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved.
358 *
359 * Redistribution and use in source and binary forms, with or without
360 * modification, are permitted provided that the following conditions
361 * are met:
362 *
363 * 1. Redistributions of source code must retain the above copyright
364 * notice, this list of conditions and the following disclaimer.
365 *
366 * 2. Redistributions in binary form must reproduce the above copyright
367 * notice, this list of conditions and the following disclaimer in
368 * the documentation and/or other materials provided with the
369 * distribution.
370 *
371 * 3. The names "VOCAL", "Vovida Open Communication Application Library",
372 * and "Vovida Open Communication Application Library (VOCAL)" must
373 * not be used to endorse or promote products derived from this
374 * software without prior written permission. For written
375 * permission, please contact vocal@vovida.org.
376 *
377 * 4. Products derived from this software may not be called "VOCAL", nor
378 * may "VOCAL" appear in their name, without prior written
379 * permission of Vovida Networks, Inc.
380 *
381 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
382 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
383 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND
384 * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA
385 * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES
386 * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
387 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
388 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
389 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
390 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
391 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
392 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
393 * DAMAGE.
394 *
395 * ====================================================================
396 *
397 * This software consists of voluntary contributions made by Vovida
398 * Networks, Inc. and many individuals on behalf of Vovida Networks,
399 * Inc. For more information on Vovida Networks, Inc., please see
400 * <http://www.vovida.org/>.
401 *
402 */
#define DebugLog(args_)
Definition Logger.hxx:128
#define InfoLog(args_)
Definition Logger.hxx:131
#define ErrLog(args_)
Definition Logger.hxx:137
#define SILO_CLEANUP_PERIOD
AsyncAddToSiloMessage(AsyncProcessor &proc, const resip::Data &tid, resip::TransactionUser *passedtu)
virtual EncodeStream & encode(EncodeStream &strm) const
output the entire message to stream
virtual EncodeStream & encode(EncodeStream &strm) const
output the entire message to stream
AsyncDrainSiloMessage(AsyncProcessor &proc, const resip::Data &tid, resip::TransactionUser *passedtu)
ContactList mRequestContacts
std::vector< SiloRecord > SiloRecordList
resip::Dispatcher * mAsyncDispatcher
MessageSilo(ProxyConfig &config, resip::Dispatcher *asyncDispatcher)
virtual processor_action_t process(RequestContext &)
virtual bool onAdd(resip::ServerRegistrationHandle, const resip::SipMessage &reg)
Called when one or more contacts are added.
virtual bool asyncProcess(AsyncProcessorMessage *msg)
virtual const resip::Data & getTransactionId() const
resip::SipMessage & getOriginalRequest()
Returns the SipMessage associated with the server transaction.
ResponseContext & getResponseContext()
void sendResponse(resip::SipMessage &response)
resip::Data getTransactionId() const
void deleteSiloRecord(time_t originalSendTime, const resip::Data &tid)
Definition SiloStore.cxx:57
void cleanupExpiredSiloRecords(uint64_t now, unsigned long expirationTime)
Definition SiloStore.cxx:64
bool addMessage(const resip::Data &destUri, const resip::Data &sourceUri, time_t originalSendTime, const resip::Data &tid, const resip::Data &mimeType, const resip::Data &messageBody)
Definition SiloStore.cxx:28
bool getSiloRecords(const resip::Data &uri, AbstractDb::SiloRecordList &recordList)
Definition SiloStore.cxx:48
resip::Data getConfigData(const resip::Data &name, const resip::Data &defaultValue, bool useDefaultIfEmpty=false) const
A single contact record, bound to an Aor during registration.
Base class for all SIP body types.
Definition Contents.hxx:51
virtual Data getBodyData() const
Definition Contents.cxx:661
const Mime & getType() const
getter for mime type of message
Definition Contents.hxx:118
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 empty() const noexcept
Checks whether the Data is empty.
Definition Data.hxx:591
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
Represents the "SIP-date" element in the RFC 3261 grammar.
A generic thread-bank class.
virtual bool post(std::unique_ptr< resip::ApplicationMessage > &work)
Posts a message to this thread bank.
resip::SipStack * mStack
static const int tagSize
bytes in to-tag& from-tag, should prob. live somewhere else
Definition Helper.hxx:42
static Data computeTag(int numBytes)
Definition Helper.cxx:713
static void makeResponse(SipMessage &response, const SipMessage &request, int responseCode, const Data &reason=Data::Empty, const Data &hostname=Data::Empty, const Data &warning=Data::Empty)
Make a response to a provided request.
Definition Helper.cxx:412
static Data computeCallId()
Definition Helper.cxx:690
Represents the "media-type" element in the RFC 3261 grammar.
Definition Mime.hxx:20
virtual void parse(ParseBuffer &pb)
Definition Mime.cxx:120
Represents the "name-addr" and the "addr-spec" elements in the RFC 3261 grammar.
Definition NameAddr.hxx:19
void remove(const Param< NameAddr > &paramType)
Definition NameAddr.cxx:485
Provides string-parsing functionality with protection from buffer overflow.
bool empty() const
return true or false indicating whether the mParsers vector is empty or not
SIP body type for holding plain contents (MIME content-type text/plain).
Represents the "Request-Line" element in the RFC 3261 grammar.
const Uri & uri() const
Represents a SIP message.
Contents * getContents() const
Retrieves the body of a SIP message.
const RequestLine & header(const RequestLineType &l) const
Returns the RequestLine. This is only valid for request messages.
resip::MethodTypes method() const
returns the method type of the message
void send(const SipMessage &msg, TransactionUser *tu=0)
allows a TU to send a message
Definition SipStack.cxx:867
The base-class for an RFC 3261 Transaction User.
FlowKey mFlowKey
This is a (largely) opaque key that subclasses of Transport will use to help record/find flows.
Definition Tuple.hxx:189
Data getAOR(bool addPort) const
Definition Uri.cxx:999
Represents the "via-parm" element in the RFC 3261 grammar.
Definition Via.hxx:18
This Class is used to implement a primitive form of RPC using loose XML formatting.
dcm! – add UnusedChecking(_enum) below;
RequestLineType h_RequestLine
Definition Headers.cxx:347
H_CallId h_CallId
Definition Headers.cxx:266
std::list< ContactInstanceRecord > ContactList
#define EncodeStream
unsigned __int64 uint64_t
Definition stdint.h:95