diff --git a/apps/webconference/RoomInfo.cpp b/apps/webconference/RoomInfo.cpp index af55b058..dc08d492 100644 --- a/apps/webconference/RoomInfo.cpp +++ b/apps/webconference/RoomInfo.cpp @@ -80,6 +80,16 @@ AmArg ConferenceRoom::asArgArray() { return res; } +vector ConferenceRoom::participantLtags() { + cleanExpired(); + vector res; + for (list::iterator it=participants.begin(); + it != participants.end(); it++) { + res.push_back(it->localtag); + } + return res; +} + void ConferenceRoom::newParticipant(const string& localtag, const string& number) { gettimeofday(&last_access_time, NULL); @@ -149,3 +159,7 @@ bool ConferenceRoom::expired(const struct timeval& now) { return (diff.tv_sec > 0) && (unsigned int)diff.tv_sec > (unsigned int)WebConferenceFactory::RoomExpiredDelay; } + +bool ConferenceRoom::hard_expired(const struct timeval& now) { + return expiry_time && (now.tv_sec > expiry_time); +} diff --git a/apps/webconference/RoomInfo.h b/apps/webconference/RoomInfo.h index 9cdd3f6b..4e3b33d4 100644 --- a/apps/webconference/RoomInfo.h +++ b/apps/webconference/RoomInfo.h @@ -8,6 +8,9 @@ using std::string; #include using std::list; +#include +using std::vector; + #include @@ -54,6 +57,7 @@ struct ConferenceRoom { string adminpin; struct timeval last_access_time; + time_t expiry_time; list participants; @@ -64,6 +68,8 @@ struct ConferenceRoom { AmArg asArgArray(); + vector participantLtags(); + void newParticipant(const string& localtag, const string& number); bool updateStatus(const string& part_tag, @@ -76,6 +82,8 @@ struct ConferenceRoom { bool expired(const struct timeval& now); bool expired(); + + bool hard_expired(const struct timeval& now); }; diff --git a/apps/webconference/WebConference.cpp b/apps/webconference/WebConference.cpp index 3ca78b68..eb3db51c 100644 --- a/apps/webconference/WebConference.cpp +++ b/apps/webconference/WebConference.cpp @@ -70,6 +70,8 @@ int WebConferenceFactory::RoomExpiredDelay; int WebConferenceFactory::RoomSweepInterval; bool WebConferenceFactory::ignore_pin = false; +bool WebConferenceFactory::PrivateRoomsMode = false; + int WebConferenceFactory::onLoad() { return getInstance()->load(); @@ -95,6 +97,7 @@ int WebConferenceFactory::load() AM_PROMPT_ADD(DROP_SOUND, WEBCONF_ANNOUNCE_PATH "beep.wav"); AM_PROMPT_ADD(ENTER_PIN, WEBCONF_ANNOUNCE_PATH "enter_pin.wav"); AM_PROMPT_ADD(WRONG_PIN, WEBCONF_ANNOUNCE_PATH "wrong_pin.wav"); + AM_PROMPT_ADD(WRONG_PIN_BYE, WEBCONF_ANNOUNCE_PATH "wrong_pin_bye.wav"); AM_PROMPT_ADD(ENTERING_CONFERENCE, WEBCONF_ANNOUNCE_PATH "entering_conference.wav"); AM_PROMPT_END(prompts, cfg, APP_NAME); @@ -207,6 +210,16 @@ int WebConferenceFactory::load() } } + + if (cfg.getParameter("private_rooms") == "yes") + PrivateRoomsMode = true; + DBG("Private rooms mode %sabled.\n", PrivateRoomsMode ? "en":"dis"); + + if (cfg.getParameter("support_rooms_timeout") == "yes") { + cleaner = new WebConferenceCleaner(this); + cleaner->start(); + } + if(cfg.hasParameter("enable_session_timer") && (cfg.getParameter("enable_session_timer") == string("yes")) ){ DBG("enabling session timers\n"); @@ -219,12 +232,27 @@ int WebConferenceFactory::load() return 0; } -void WebConferenceFactory::newParticipant(const string& conf_id, +bool WebConferenceFactory::isValidConference(const string& conf_id) { + if (!PrivateRoomsMode) + return true; + rooms_mut.lock(); + bool res = rooms.find(conf_id) != rooms.end(); + rooms_mut.unlock(); + return res; +} + +bool WebConferenceFactory::newParticipant(const string& conf_id, const string& localtag, const string& number) { rooms_mut.lock(); + if (PrivateRoomsMode && rooms.find(conf_id) == rooms.end()) { + rooms_mut.unlock(); + return false; + } + rooms[conf_id].newParticipant(localtag, number); rooms_mut.unlock(); + return true; } void WebConferenceFactory::updateStatus(const string& conf_id, @@ -232,7 +260,9 @@ void WebConferenceFactory::updateStatus(const string& conf_id, ConferenceRoomParticipant::ParticipantStatus status, const string& reason) { rooms_mut.lock(); - rooms[conf_id].updateStatus(localtag, status, reason); + if (!PrivateRoomsMode || rooms.find(conf_id) != rooms.end()) { + rooms[conf_id].updateStatus(localtag, status, reason); + } rooms_mut.unlock(); } @@ -247,16 +277,21 @@ string WebConferenceFactory::getAdminpin(const string& room) { } ConferenceRoom* WebConferenceFactory::getRoom(const string& room, - const string& adminpin) { + const string& adminpin, + bool ignore_adminpin = false) { ConferenceRoom* res = NULL; map::iterator it = rooms.find(room); if (it == rooms.end()) { + if (PrivateRoomsMode) + return NULL; + // (re)open room rooms[room] = ConferenceRoom(); rooms[room].adminpin = adminpin; res = &rooms[room]; } else { if ((!ignore_pin) && + (!ignore_adminpin) && (!it->second.adminpin.empty()) && (it->second.adminpin != adminpin)) { // wrong pin @@ -372,7 +407,7 @@ void WebConferenceFactory::invoke(const string& method, { - if(method == "roomCreate"){ + if (method == "roomCreate"){ args.assertArrayFmt("s"); roomCreate(args, ret); ret.push(getServerInfoString().c_str()); @@ -380,6 +415,10 @@ void WebConferenceFactory::invoke(const string& method, args.assertArrayFmt("ss"); roomInfo(args, ret); ret.push(getServerInfoString().c_str()); + } else if(method == "roomDelete"){ + args.assertArrayFmt("ss"); + roomDelete(args, ret); + ret.push(getServerInfoString().c_str()); } else if(method == "dialout"){ args.assertArrayFmt("sssssss"); dialout(args, ret); @@ -434,6 +473,7 @@ void WebConferenceFactory::invoke(const string& method, ret.push(getServerInfoString().c_str()); } else if(method == "_list"){ ret.push("roomCreate"); + ret.push("roomDelete"); ret.push("roomInfo"); ret.push("dialout"); ret.push("mute"); @@ -492,6 +532,13 @@ void WebConferenceFactory::sweepRooms() { void WebConferenceFactory::roomCreate(const AmArg& args, AmArg& ret) { string room = args.get(0).asCStr(); + time_t expiry_time = 0; + if (args.size() > 1 && (args.get(1).asInt() > 0)) { + struct timeval now; + gettimeofday(&now, NULL); + expiry_time = now.tv_sec + args.get(1).asInt(); + } + rooms_mut.lock(); // sweep rooms (if necessary) @@ -501,6 +548,7 @@ void WebConferenceFactory::roomCreate(const AmArg& args, AmArg& ret) { if (it == rooms.end()) { rooms[room] = ConferenceRoom(); rooms[room].adminpin = getRandomPin(); + rooms[room].expiry_time = expiry_time; ret.push(0); ret.push("OK"); ret.push(rooms[room].adminpin.c_str()); @@ -523,21 +571,76 @@ void WebConferenceFactory::roomInfo(const AmArg& args, AmArg& ret) { string room = args.get(0).asCStr(); string adminpin = args.get(1).asCStr(); - rooms_mut.lock(); - ConferenceRoom* r = getRoom(room, adminpin); - if (NULL == r) { + rooms_mut.lock(); + ConferenceRoom* r = getRoom(room, adminpin); + if (NULL == r) { ret.push(1); - ret.push("wrong adminpin"); + ret.push("wrong adminpin or inexisting room"); // for consistency, add an empty array AmArg a; a.assertArray(0); ret.push(a); - } else { - ret.push(0); - ret.push("OK"); - ret.push(r->asArgArray()); - } - rooms_mut.unlock(); + } else { + ret.push(0); + ret.push("OK"); + ret.push(r->asArgArray()); + } + rooms_mut.unlock(); +} + +void WebConferenceFactory::roomDelete(const string& room, const string& adminpin, + AmArg& ret, bool ignore_adminpin = false) { + rooms_mut.lock(); + + map::iterator it = rooms.find(room); + if (it == rooms.end()) { + rooms_mut.unlock(); + ret.push(2); + ret.push("room does not exist\n"); + return; + } + + rooms_mut.unlock(); + + postAllConfEvent(room, adminpin, ret, + WebConferenceEvent::Kick, ignore_adminpin); + + if (ret.get(0).asInt()==0) { + DBG("erasing room '%s'\n", room.c_str()); + rooms_mut.lock(); + rooms.erase(room); + rooms_mut.unlock(); + } +} + +void WebConferenceFactory::roomDelete(const AmArg& args, AmArg& ret) { + rooms_mut.lock(); + string room = args.get(0).asCStr(); + string adminpin = args.get(1).asCStr(); + + roomDelete(room, adminpin, ret); +} + +void WebConferenceFactory::closeExpiredRooms() { + struct timeval now; + gettimeofday(&now, NULL); + + vector expired_rooms; + + rooms_mut.lock(); + for (map::iterator it = + rooms.begin(); it !=rooms.end(); it++) { + if (it->second.hard_expired(now)) + expired_rooms.push_back(it->first); + } + rooms_mut.unlock(); + + for (vector::iterator it= + expired_rooms.begin(); it != expired_rooms.end(); it++) { + DBG("deleting expired room '%s'\n", it->c_str()); + AmArg ret; + roomDelete(*it, "", ret, true); + } } void WebConferenceFactory::dialout(const AmArg& args, AmArg& ret) { @@ -587,11 +690,12 @@ void WebConferenceFactory::dialout(const AmArg& args, AmArg& ret) { // sweep rooms (if necessary) sweepRooms(); - ConferenceRoom* r = getRoom(room, adminpin); + bool is_valid_room = getRoom(room, adminpin) != NULL; rooms_mut.unlock(); - if (NULL == r) { + + if (!is_valid_room) { ret.push(1); - ret.push("wrong adminpin"); + ret.push("wrong adminpin or inexisting room"); ret.push(""); return; } @@ -624,6 +728,29 @@ void WebConferenceFactory::dialout(const AmArg& args, AmArg& ret) { } } +void WebConferenceFactory::postAllConfEvent(const string& room, const string& adminpin, + AmArg& ret, int id, bool ignore_adminpin = false) { + vector ltags; + rooms_mut.lock(); { + ConferenceRoom* r = getRoom(room, adminpin, ignore_adminpin); + if (NULL == r) { + rooms_mut.unlock(); + return; + } + + ltags = r->participantLtags(); + } rooms_mut.unlock(); + + for (vector::iterator it = + ltags.begin(); it != ltags.end(); it++) { + AmSessionContainer::instance()->postEvent(*it, + new WebConferenceEvent(id)); + } + + ret.push(0); + ret.push("OK"); +} + void WebConferenceFactory::postConfEvent(const AmArg& args, AmArg& ret, int id, int mute) { string room = args.get(0).asCStr(); @@ -635,16 +762,15 @@ void WebConferenceFactory::postConfEvent(const AmArg& args, AmArg& ret, rooms_mut.lock(); ConferenceRoom* r = getRoom(room, adminpin); if (NULL == r) { - ret.push(1); - ret.push("wrong adminpin"); - rooms_mut.unlock(); - return; + ret.push(1); + ret.push("wrong adminpin or inexisting room"); + rooms_mut.unlock(); + return; } bool p_exists = r->hasParticipant(call_tag); if (p_exists && (mute >= 0)) r->setMuted(call_tag, mute); - - rooms_mut.unlock(); + rooms_mut.unlock(); if (p_exists) { AmSessionContainer::instance()->postEvent(call_tag, @@ -681,7 +807,7 @@ void WebConferenceFactory::getRoomPassword(const AmArg& args, AmArg& ret) { if ((!MasterPassword.length()) || pwd != MasterPassword) { ret.push(403); - ret.push("Wrong Master Password.\n"); + ret.push("Wrong Master Password."); return; } int res_code = 404; @@ -707,7 +833,7 @@ void WebConferenceFactory::changeRoomAdminpin(const AmArg& args, AmArg& ret) { ConferenceRoom* r = getRoom(room, adminpin); if (NULL == r) { ret.push(1); - ret.push("wrong adminpin"); + ret.push("wrong adminpin or inexisting room"); } else { r->adminpin = new_adminpin; ret.push(0); @@ -724,13 +850,14 @@ void WebConferenceFactory::listRooms(const AmArg& args, AmArg& ret) { pwd != MasterPassword) { ret.push(407); AmArg res; - res.push("Wrong Master Password.\n"); + res.push("Wrong Master Password."); ret.push(res); return; } AmArg room_list; - + room_list.assertArray(); + rooms_mut.lock(); for (map::iterator it = rooms.begin(); it != rooms.end(); it++) { @@ -822,3 +949,14 @@ void WebConferenceFactory::callStats(bool success, unsigned int connect_t) { } } +void WebConferenceCleaner::on_stop() { + is_stopped.set(true); +} + +void WebConferenceCleaner::run(){ + sleep(1); + while (!is_stopped.get()) { + factory->closeExpiredRooms(); + sleep(1); + } +} diff --git a/apps/webconference/WebConference.h b/apps/webconference/WebConference.h index c4903767..2d251931 100644 --- a/apps/webconference/WebConference.h +++ b/apps/webconference/WebConference.h @@ -54,6 +54,7 @@ class ConferenceStatusContainer; #define DROP_SOUND "drop_sound" #define ENTER_PIN "enter_pin" #define WRONG_PIN "wrong_pin" +#define WRONG_PIN_BYE "wrong_pin_bye" // default path for files #define WEBCONF_ANNOUNCE_PATH "/usr/local/lib/sems/audio/webconference/" @@ -68,6 +69,8 @@ public: }; }; +class WebConferenceCleaner; + class WebConferenceFactory : public AmSessionFactory, public AmDynInvokeFactory, @@ -82,6 +85,8 @@ class WebConferenceFactory AmSessionEventHandlerFactory* session_timer_f; + WebConferenceCleaner* cleaner; + // for DI static WebConferenceFactory* _instance; bool configured; @@ -91,9 +96,13 @@ class WebConferenceFactory string getRandomPin(); /** @return NULL if adminpin wrong */ ConferenceRoom* getRoom(const string& room, - const string& adminpin); + const string& adminpin, + bool ignore_adminpin); void postConfEvent(const AmArg& args, AmArg& ret, int id, int mute); + /** broadcast conf event to all */ + void postAllConfEvent(const string& room, const string& adminpin, + AmArg& ret, int id, bool ignore_adminpin); regex_t direct_room_re; bool use_direct_room; @@ -126,13 +135,16 @@ public: static bool ignore_pin; + static bool PrivateRoomsMode; + WebConferenceFactory(const string& _app_name); AmSession* onInvite(const AmSipRequest&); AmSession* onInvite(const AmSipRequest& req, AmArg& session_params); int onLoad(); - void newParticipant(const string& conf_id, + bool isValidConference(const string& conf_id); + bool newParticipant(const string& conf_id, const string& localtag, const string& number); void updateStatus(const string& conf_id, @@ -142,33 +154,58 @@ public: void callStats(bool success, unsigned int connect_t); + void closeExpiredRooms(); + // DI API WebConferenceFactory* getInstance(){ return _instance; } void invoke(const string& method, const AmArg& args, AmArg& ret); + void roomDelete(const string& room, const string& adminpin, + AmArg& ret, bool ignore_adminpin); + // DI functions void roomCreate(const AmArg& args, AmArg& ret); void roomInfo(const AmArg& args, AmArg& ret); + void roomDelete(const AmArg& args, AmArg& ret); + void changeRoomAdminpin(const AmArg& args, AmArg& ret); + void dialout(const AmArg& args, AmArg& ret); void kickout(const AmArg& args, AmArg& ret); void mute(const AmArg& args, AmArg& ret); void unmute(const AmArg& args, AmArg& ret); - void changeRoomAdminpin(const AmArg& args, AmArg& ret); + void serverInfo(const AmArg& args, AmArg& ret); + void vqRoomFeedback(const AmArg& args, AmArg& ret); void vqCallFeedback(const AmArg& args, AmArg& ret); void vqConferenceFeedback(const AmArg& args, AmArg& ret); void resetFeedback(const AmArg& args, AmArg& ret); void flushFeedback(const AmArg& args, AmArg& ret); + void getRoomPassword(const AmArg& args, AmArg& ret); void listRooms(const AmArg& args, AmArg& ret); }; +class WebConferenceCleaner + : public AmThread +{ + WebConferenceFactory* factory; + AmCondition is_stopped; + +public: + WebConferenceCleaner(WebConferenceFactory* factory) + : factory(factory), is_stopped(false) { } + + void run(); + void on_stop(); +}; + #endif // Local Variables: // mode:C++ // End: + diff --git a/apps/webconference/WebConferenceDialog.cpp b/apps/webconference/WebConferenceDialog.cpp index 3961b750..9431d890 100644 --- a/apps/webconference/WebConferenceDialog.cpp +++ b/apps/webconference/WebConferenceDialog.cpp @@ -114,16 +114,23 @@ void WebConferenceDialog::onSessionStart(const AmSipRequest& req) { // direct room access? if (conf_id.empty()) { - state = EnteringPin; + state = EnteringPin; prompts.addToPlaylist(ENTER_PIN, (long)this, play_list); // set the playlist as input and output setInOut(&play_list,&play_list); } else { - DBG("########## direct connect conference #########\n"); - factory->newParticipant(conf_id, - getLocalTag(), - dlg.remote_party); - factory->updateStatus(conf_id, + DBG("########## direct connect conference #########\n"); + if (!factory->newParticipant(conf_id, + getLocalTag(), + dlg.remote_party)) { + DBG("inexisting conference room\n"); + state = PlayErrorFinish; + setInOut(&play_list,&play_list); + prompts.addToPlaylist(WRONG_PIN_BYE, (long)this, play_list); + return; + } + + factory->updateStatus(conf_id, getLocalTag(), ConferenceRoomParticipant::Connected, "direct access: entered"); @@ -266,21 +273,36 @@ void WebConferenceDialog::process(AmEvent* ev) if (EnteringConference == state) { state = InConference; DBG("########## connectConference after pin entry #########\n"); + + if (!factory->newParticipant(pin_str, + getLocalTag(), + dlg.remote_party)) { + DBG("inexisting conference room\n"); + state = PlayErrorFinish; + setInOut(&play_list,&play_list); + prompts.addToPlaylist(WRONG_PIN_BYE, (long)this, play_list); + return; + } + connectConference(pin_str); - factory->newParticipant(pin_str, - getLocalTag(), - dlg.remote_party); factory->updateStatus(pin_str, getLocalTag(), ConferenceRoomParticipant::Connected, "entered"); - } + } } // audio events AmAudioEvent* audio_ev = dynamic_cast(ev); if (audio_ev && audio_ev->event_id == AmAudioEvent::noAudio) { DBG("########## noAudio event #########\n"); + if (PlayErrorFinish == state) { + DBG("Finished playing bye message, ending call.\n"); + dlg.bye(); + setStopped(); + return; + } + return; } @@ -312,10 +334,9 @@ void WebConferenceDialog::onDtmf(int event, int duration) play_list.close(false); } else if (event==10 || event==11) { // pound and star key - // if required add checking of pin here... - if (!pin_str.length()) { - + if (!pin_str.length() || !factory->isValidConference(pin_str)) { prompts.addToPlaylist(WRONG_PIN, (long)this, play_list, true); + pin_str.clear(); } else { state = EnteringConference; setInOut(NULL, NULL); diff --git a/apps/webconference/WebConferenceDialog.h b/apps/webconference/WebConferenceDialog.h index 8e2fb0a9..23cb4796 100644 --- a/apps/webconference/WebConferenceDialog.h +++ b/apps/webconference/WebConferenceDialog.h @@ -48,7 +48,8 @@ public: EnteringConference, InConference, InConferenceRinging, - InConferenceEarly + InConferenceEarly, + PlayErrorFinish }; private: diff --git a/apps/webconference/etc/webconference.conf b/apps/webconference/etc/webconference.conf index c81f33d3..5b53d1f7 100644 --- a/apps/webconference/etc/webconference.conf +++ b/apps/webconference/etc/webconference.conf @@ -5,6 +5,7 @@ join_sound=/usr/local/lib/sems/audio/webconference/beep.wav drop_sound=/usr/local/lib/sems/audio/webconference/beep.wav enter_pin=/usr/local/lib/sems/audio/webconference/pin_prompt.wav wrong_pin=/usr/local/lib/sems/audio/webconference/wrong_pin.wav +wrong_pin_bye=/usr/local/lib/sems/audio/webconference/wrong_pin_bye.wav entering_conference=/usr/local/lib/sems/audio/webconference/entering_conference.wav # @@ -65,6 +66,23 @@ stats_dir=/var/log/sems-webconference/ # #webconference_urlbase=https://webconference.iptel.org/openRoom.py?action=openRoom +# private_rooms = [yes |no] +# +# In private rooms mode, participants can join rooms only if they have been +# opened before. +# +# Default: no +# +# private_rooms=yes + +# support_rooms_timeout = [yes |no] +# +# Support timeout for rooms? If rooms are timed out, they will be closed and +# all participants disconnected. +# +# Default: no +# +# support_rooms_timeout=yes # # participants_expire = [yes | no] diff --git a/apps/webconference/wav/wrong_pin_bye.wav b/apps/webconference/wav/wrong_pin_bye.wav new file mode 100644 index 00000000..52c8321b Binary files /dev/null and b/apps/webconference/wav/wrong_pin_bye.wav differ