rob@76
|
1 /*
|
rob@76
|
2 OSCgroups -- open sound control groupcasting infrastructure
|
rob@76
|
3 Copyright (C) 2005 Ross Bencina
|
rob@76
|
4
|
rob@76
|
5 This program is free software; you can redistribute it and/or
|
rob@76
|
6 modify it under the terms of the GNU General Public License
|
rob@76
|
7 as published by the Free Software Foundation; either version 2
|
rob@76
|
8 of the License, or (at your option) any later version.
|
rob@76
|
9
|
rob@76
|
10 This program is distributed in the hope that it will be useful,
|
rob@76
|
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
rob@76
|
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
rob@76
|
13 GNU General Public License for more details.
|
rob@76
|
14
|
rob@76
|
15 You should have received a copy of the GNU General Public License
|
rob@76
|
16 along with this program; if not, write to the Free Software
|
rob@76
|
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
rob@76
|
18 */
|
rob@76
|
19
|
rob@76
|
20 #include "GroupServer.h"
|
rob@76
|
21
|
rob@76
|
22 #include <string>
|
rob@76
|
23 #include <set>
|
rob@76
|
24 #include <iostream>
|
rob@76
|
25 #include <ctime>
|
rob@76
|
26 #include <algorithm>
|
rob@76
|
27 #include <iterator>
|
rob@76
|
28 #include <cassert>
|
rob@76
|
29 #include <cstring>
|
rob@76
|
30
|
rob@76
|
31 #include "ip/NetworkingUtils.h"
|
rob@76
|
32
|
rob@76
|
33 #if defined(__BORLANDC__) // workaround for BCB4 release build intrinsics bug
|
rob@76
|
34 namespace std {
|
rob@76
|
35 using ::__strcpy__; // avoid error: E2316 '__strcpy__' is not a member of 'std'.
|
rob@76
|
36 }
|
rob@76
|
37 #endif
|
rob@76
|
38
|
rob@76
|
39 /*
|
rob@76
|
40 TODO:
|
rob@76
|
41 switch to using char * instead of string:: to avoid allocating new strings
|
rob@76
|
42 every time we execute a find()
|
rob@76
|
43 */
|
rob@76
|
44
|
rob@76
|
45
|
rob@76
|
46 static std::ostream& Log()
|
rob@76
|
47 {
|
rob@76
|
48 std::time_t t;
|
rob@76
|
49 std::time( &t );
|
rob@76
|
50
|
rob@76
|
51 // ctime() returns a constant width 26 char string including trailing \0
|
rob@76
|
52 // the fields are all constant width.
|
rob@76
|
53 char s[26];
|
rob@76
|
54 std::strcpy( s, std::ctime( &t ) );
|
rob@76
|
55 s[24] = '\0'; // remove trailing null
|
rob@76
|
56
|
rob@76
|
57 std::cout << s << ": ";
|
rob@76
|
58 return std::cout;
|
rob@76
|
59 }
|
rob@76
|
60
|
rob@76
|
61
|
rob@76
|
62 GroupServer::GroupServer( int timeoutSeconds, int maxUsers, int maxGroups )
|
rob@76
|
63 : timeoutSeconds_( timeoutSeconds )
|
rob@76
|
64 , maxUsers_( maxUsers )
|
rob@76
|
65 , maxGroups_( maxGroups )
|
rob@76
|
66 , userCount_( 0 )
|
rob@76
|
67 , groupCount_( 0 )
|
rob@76
|
68 {
|
rob@76
|
69
|
rob@76
|
70 }
|
rob@76
|
71
|
rob@76
|
72
|
rob@76
|
73 GroupServer::~GroupServer()
|
rob@76
|
74 {
|
rob@76
|
75 for( const_user_iterator i = users_begin(); i != users_end(); ++i )
|
rob@76
|
76 delete i->second;
|
rob@76
|
77 for( const_group_iterator i = groups_begin(); i != groups_end(); ++i )
|
rob@76
|
78 delete i->second;
|
rob@76
|
79 }
|
rob@76
|
80
|
rob@76
|
81
|
rob@76
|
82 User *GroupServer::CreateUser( const char *userName, const char *userPassword )
|
rob@76
|
83 {
|
rob@76
|
84 Log() << "creating user '" << userName << "'." << std::endl;
|
rob@76
|
85
|
rob@76
|
86 User *user = new User( userName, userPassword );
|
rob@76
|
87 std::pair<user_iterator,bool> r =
|
rob@76
|
88 users_.insert( std::make_pair( user->name, user ) );
|
rob@76
|
89 // we assume the caller didn't try to create a user with an existing name
|
rob@76
|
90 assert( r.second == true );
|
rob@76
|
91 ++userCount_;
|
rob@76
|
92
|
rob@76
|
93 return user;
|
rob@76
|
94 }
|
rob@76
|
95
|
rob@76
|
96
|
rob@76
|
97 User *GroupServer::FindUser( const char *userName )
|
rob@76
|
98 {
|
rob@76
|
99 user_iterator user = users_.find( userName );
|
rob@76
|
100
|
rob@76
|
101 return ( user != users_.end() ) ? user->second : (User*) 0;
|
rob@76
|
102 }
|
rob@76
|
103
|
rob@76
|
104
|
rob@76
|
105 void GroupServer::PurgeStaleUsers()
|
rob@76
|
106 {
|
rob@76
|
107 std::time_t currentTime = std::time(0);
|
rob@76
|
108
|
rob@76
|
109 for( std::map< std::string, User* >::iterator i = users_.begin();
|
rob@76
|
110 i != users_.end(); /* nothing */ ){
|
rob@76
|
111
|
rob@76
|
112 unsigned long inactiveSeconds =
|
rob@76
|
113 i->second->SecondsSinceLastAliveReceived( currentTime );
|
rob@76
|
114 if( inactiveSeconds >= (unsigned int)timeoutSeconds_ ){
|
rob@76
|
115 Log() << "purging user '" << i->second->name << "' after "
|
rob@76
|
116 << inactiveSeconds << " seconds of inactivity." << std::endl;
|
rob@76
|
117
|
rob@76
|
118 SeparateUserFromAllGroups( i->second );
|
rob@76
|
119 delete i->second;
|
rob@76
|
120 --userCount_;
|
rob@76
|
121
|
rob@76
|
122 // i think this is safe, we increment i before erasing the
|
rob@76
|
123 // element referenced by its old value...
|
rob@76
|
124 std::map< std::string, User* >::iterator j = i;
|
rob@76
|
125 ++i;
|
rob@76
|
126 users_.erase( j );
|
rob@76
|
127 }else{
|
rob@76
|
128 ++i;
|
rob@76
|
129 }
|
rob@76
|
130 }
|
rob@76
|
131 }
|
rob@76
|
132
|
rob@76
|
133
|
rob@76
|
134 Group *GroupServer::CreateGroup(
|
rob@76
|
135 const char *groupName, const char *groupPassword )
|
rob@76
|
136 {
|
rob@76
|
137 Log() << "creating group '" << groupName << "'." << std::endl;
|
rob@76
|
138
|
rob@76
|
139 Group *group = new Group( groupName, groupPassword );
|
rob@76
|
140 std::pair<group_iterator, bool> r =
|
rob@76
|
141 groups_.insert( std::make_pair( group->name, group ) );
|
rob@76
|
142 // we assume the caller didn't try to create an existing group
|
rob@76
|
143 assert( r.second == true );
|
rob@76
|
144 ++groupCount_;
|
rob@76
|
145
|
rob@76
|
146 return group;
|
rob@76
|
147 }
|
rob@76
|
148
|
rob@76
|
149
|
rob@76
|
150 Group *GroupServer::FindGroup( const char *groupName )
|
rob@76
|
151 {
|
rob@76
|
152 group_iterator group = groups_.find( groupName );
|
rob@76
|
153
|
rob@76
|
154 return ( group != groups_.end() ) ? group->second : (Group*) 0;
|
rob@76
|
155 }
|
rob@76
|
156
|
rob@76
|
157
|
rob@76
|
158 // The User <-> Group relation is managed as a bidirectional link (each User
|
rob@76
|
159 // contains a set of groups which it is associated with, and each Group
|
rob@76
|
160 // maintains a set of Users which it is associated with). The Associate*
|
rob@76
|
161 // and Separate* methods below create and destroy the bidirectional link
|
rob@76
|
162 // RemoveUserReferenceFromGroup is a helper function which only destroys
|
rob@76
|
163 // one side of the link.
|
rob@76
|
164
|
rob@76
|
165 void GroupServer::AssociateUserWithGroup( User *user, Group* group )
|
rob@76
|
166 {
|
rob@76
|
167 Log() << "adding user '" << user->name
|
rob@76
|
168 << "' to group '" << group->name << "'." << std::endl;
|
rob@76
|
169
|
rob@76
|
170 user->groups_.insert( group );
|
rob@76
|
171 group->users_.insert( user );
|
rob@76
|
172 }
|
rob@76
|
173
|
rob@76
|
174
|
rob@76
|
175 void GroupServer::RemoveUserReferenceFromGroup( User *user, Group* group )
|
rob@76
|
176 {
|
rob@76
|
177 Log() << "removing user '" << user->name
|
rob@76
|
178 << "' from group '" << group->name << "'." << std::endl;
|
rob@76
|
179
|
rob@76
|
180 std::set< User* >::iterator i = group->users_.find( user );
|
rob@76
|
181 assert( i != group->users_.end() );
|
rob@76
|
182
|
rob@76
|
183 group->users_.erase( i );
|
rob@76
|
184
|
rob@76
|
185 if( group->users_.empty() ){
|
rob@76
|
186 Log() << "purging empty group '" << group->name << "'." << std::endl;
|
rob@76
|
187
|
rob@76
|
188 groups_.erase( group->name );
|
rob@76
|
189 delete group;
|
rob@76
|
190 --groupCount_;
|
rob@76
|
191 }
|
rob@76
|
192 }
|
rob@76
|
193
|
rob@76
|
194
|
rob@76
|
195 void GroupServer::SeparateUserFromGroup( User *user, Group* group )
|
rob@76
|
196 {
|
rob@76
|
197 user->groups_.erase( group );
|
rob@76
|
198 RemoveUserReferenceFromGroup( user, group );
|
rob@76
|
199 }
|
rob@76
|
200
|
rob@76
|
201
|
rob@76
|
202 void GroupServer::SeparateUserFromAllGroups( User *user )
|
rob@76
|
203 {
|
rob@76
|
204 for( std::set<Group*>::iterator i = user->groups_.begin();
|
rob@76
|
205 i != user->groups_.end(); ++i ){
|
rob@76
|
206
|
rob@76
|
207 RemoveUserReferenceFromGroup( user, *i);
|
rob@76
|
208 }
|
rob@76
|
209
|
rob@76
|
210 user->groups_.clear();
|
rob@76
|
211 }
|
rob@76
|
212
|
rob@76
|
213
|
rob@76
|
214 static void UpdateEndpoint( IpEndpointName& dest, const IpEndpointName& src,
|
rob@76
|
215 const char *userName, const char *whichEndpoint )
|
rob@76
|
216 {
|
rob@76
|
217 if( dest != src ){
|
rob@76
|
218
|
rob@76
|
219 char endpointString[ IpEndpointName::ADDRESS_AND_PORT_STRING_LENGTH ];
|
rob@76
|
220 src.AddressAndPortAsString( endpointString );
|
rob@76
|
221
|
rob@76
|
222 Log() << "updating " << whichEndpoint << " endpoint for user '"
|
rob@76
|
223 << userName << "' to " << endpointString << "." << std::endl;
|
rob@76
|
224
|
rob@76
|
225 dest = src;
|
rob@76
|
226 }
|
rob@76
|
227 }
|
rob@76
|
228
|
rob@76
|
229
|
rob@76
|
230 GroupServer::UserStatus GroupServer::UserAlive(
|
rob@76
|
231 const char *userName, const char *userPassword,
|
rob@76
|
232 const IpEndpointName& privateEndpoint,
|
rob@76
|
233 const IpEndpointName& publicEndpoint,
|
rob@76
|
234 const char **groupNamesAndPasswords,
|
rob@76
|
235 UserStatus *userGroupsStatus, int groupCount )
|
rob@76
|
236 {
|
rob@76
|
237 // find or create the user, aborting if the password for an existing
|
rob@76
|
238 // user is incorrect, or if the maximum number of users has been reached
|
rob@76
|
239
|
rob@76
|
240 User *user = FindUser( userName );
|
rob@76
|
241 if( user ){
|
rob@76
|
242 if( user->password.compare( userPassword ) != 0 ){
|
rob@76
|
243 Log() << "attempt to update user '"
|
rob@76
|
244 << userName << "' with incorrect password." << std::endl;
|
rob@76
|
245 return USER_STATUS_WRONG_PASSWORD;
|
rob@76
|
246 }
|
rob@76
|
247 }else{
|
rob@76
|
248 if( userCount_ == maxUsers_ ){
|
rob@76
|
249 Log() << "user limit reached, user '"
|
rob@76
|
250 << userName << "' not admitted." << std::endl;
|
rob@76
|
251 return USER_STATUS_SERVER_LIMIT_REACHED;
|
rob@76
|
252 }else{
|
rob@76
|
253 user = CreateUser( userName, userPassword );
|
rob@76
|
254 }
|
rob@76
|
255 }
|
rob@76
|
256
|
rob@76
|
257 UpdateEndpoint(
|
rob@76
|
258 user->privateEndpoint, privateEndpoint, userName, "private" );
|
rob@76
|
259 UpdateEndpoint( user->publicEndpoint, publicEndpoint, userName, "public" );
|
rob@76
|
260
|
rob@76
|
261 user->lastAliveMessageArrivalTime = time(0);
|
rob@76
|
262
|
rob@76
|
263
|
rob@76
|
264 // previousButNotCurrentGroups begins containing all the groups the user
|
rob@76
|
265 // was in before the current message was received. We remove those which
|
rob@76
|
266 // the user is still a member of, leaving the set that the user is no
|
rob@76
|
267 // longer a member of. We then use the set to remove associations with
|
rob@76
|
268 // non-current groups.
|
rob@76
|
269
|
rob@76
|
270 std::set<Group*> previousButNotCurrentGroups( user->groups_ );
|
rob@76
|
271
|
rob@76
|
272 for( int i=0; i < groupCount; ++i ){
|
rob@76
|
273 const char *groupName = groupNamesAndPasswords[i*2];
|
rob@76
|
274 const char *groupPassword = groupNamesAndPasswords[i*2 + 1];
|
rob@76
|
275
|
rob@76
|
276 Group *group = FindGroup( groupName );
|
rob@76
|
277 if( group ){
|
rob@76
|
278 if( user->IsMemberOf( group ) ){
|
rob@76
|
279 // check that previousButNotCurrentGroups contains group before
|
rob@76
|
280 // removing it. it won't if we were passed the same group
|
rob@76
|
281 // multiple times
|
rob@76
|
282 std::set<Group*>::iterator j =
|
rob@76
|
283 previousButNotCurrentGroups.find( group );
|
rob@76
|
284 if( j != previousButNotCurrentGroups.end() )
|
rob@76
|
285 previousButNotCurrentGroups.erase( j );
|
rob@76
|
286
|
rob@76
|
287 userGroupsStatus[i] = USER_STATUS_OK;
|
rob@76
|
288 }else{
|
rob@76
|
289 // user isn't in group so join it
|
rob@76
|
290 if( group->password.compare( groupPassword ) == 0 ){
|
rob@76
|
291 AssociateUserWithGroup( user, group );
|
rob@76
|
292 userGroupsStatus[i] = USER_STATUS_OK;
|
rob@76
|
293 }else{
|
rob@76
|
294 userGroupsStatus[i] = USER_STATUS_WRONG_PASSWORD;
|
rob@76
|
295 }
|
rob@76
|
296 }
|
rob@76
|
297 }else{ // group doesn't exist
|
rob@76
|
298 if( groupCount_ == maxGroups_ ){
|
rob@76
|
299 Log() << "group limit reached, group '"
|
rob@76
|
300 << groupName << "' not created." << std::endl;
|
rob@76
|
301 userGroupsStatus[i] = USER_STATUS_SERVER_LIMIT_REACHED;
|
rob@76
|
302 }else{
|
rob@76
|
303 group = CreateGroup( groupName, groupPassword );
|
rob@76
|
304 AssociateUserWithGroup( user, group );
|
rob@76
|
305 userGroupsStatus[i] = USER_STATUS_OK;
|
rob@76
|
306 }
|
rob@76
|
307 }
|
rob@76
|
308 }
|
rob@76
|
309
|
rob@76
|
310 for( std::set<Group*>::iterator j = previousButNotCurrentGroups.begin();
|
rob@76
|
311 j != previousButNotCurrentGroups.end(); ++j ){
|
rob@76
|
312
|
rob@76
|
313 SeparateUserFromGroup( user, *j );
|
rob@76
|
314 }
|
rob@76
|
315
|
rob@76
|
316 return USER_STATUS_OK;
|
rob@76
|
317 }
|
rob@76
|
318
|