aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenGridServices/OpenGridServices.UserServer/UserManager.cs
diff options
context:
space:
mode:
authorMW2007-06-08 16:49:24 +0000
committerMW2007-06-08 16:49:24 +0000
commita8cabbd600f2bf4e3ecd8b48d726f9c1036d9f93 (patch)
treed9c04b9241c67f892285bc2b9fb05b1e70e40576 /OpenGridServices/OpenGridServices.UserServer/UserManager.cs
parentprebuild.xml should be fixed/updated. (diff)
downloadopensim-SC-a8cabbd600f2bf4e3ecd8b48d726f9c1036d9f93.zip
opensim-SC-a8cabbd600f2bf4e3ecd8b48d726f9c1036d9f93.tar.gz
opensim-SC-a8cabbd600f2bf4e3ecd8b48d726f9c1036d9f93.tar.bz2
opensim-SC-a8cabbd600f2bf4e3ecd8b48d726f9c1036d9f93.tar.xz
Deleted OpenGridServices folder as the easiest way to reimport the latest version from trunk
Diffstat (limited to '')
-rw-r--r--OpenGridServices/OpenGridServices.UserServer/UserManager.cs597
1 files changed, 0 insertions, 597 deletions
diff --git a/OpenGridServices/OpenGridServices.UserServer/UserManager.cs b/OpenGridServices/OpenGridServices.UserServer/UserManager.cs
deleted file mode 100644
index fdda63b..0000000
--- a/OpenGridServices/OpenGridServices.UserServer/UserManager.cs
+++ /dev/null
@@ -1,597 +0,0 @@
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Text;
5using OpenGrid.Framework.Data;
6using libsecondlife;
7using System.Reflection;
8
9using System.Xml;
10using Nwc.XmlRpc;
11using OpenSim.Framework.Sims;
12using OpenSim.Framework.Inventory;
13using OpenSim.Framework.Utilities;
14
15using System.Security.Cryptography;
16
17namespace OpenGridServices.UserServer
18{
19 public class UserManager
20 {
21 public OpenSim.Framework.Interfaces.UserConfig _config;
22 Dictionary<string, IUserData> _plugins = new Dictionary<string, IUserData>();
23
24 /// <summary>
25 /// Adds a new user server plugin - user servers will be requested in the order they were loaded.
26 /// </summary>
27 /// <param name="FileName">The filename to the user server plugin DLL</param>
28 public void AddPlugin(string FileName)
29 {
30 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Userstorage: Attempting to load " + FileName);
31 Assembly pluginAssembly = Assembly.LoadFrom(FileName);
32
33 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Userstorage: Found " + pluginAssembly.GetTypes().Length + " interfaces.");
34 foreach (Type pluginType in pluginAssembly.GetTypes())
35 {
36 if (!pluginType.IsAbstract)
37 {
38 Type typeInterface = pluginType.GetInterface("IUserData", true);
39
40 if (typeInterface != null)
41 {
42 IUserData plug = (IUserData)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));
43 plug.Initialise();
44 this._plugins.Add(plug.getName(), plug);
45 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Userstorage: Added IUserData Interface");
46 }
47
48 typeInterface = null;
49 }
50 }
51
52 pluginAssembly = null;
53 }
54
55 /// <summary>
56 ///
57 /// </summary>
58 /// <param name="user"></param>
59 public void AddUserProfile(string firstName, string lastName, string pass, uint regX, uint regY)
60 {
61 UserProfileData user = new UserProfileData();
62 user.homeLocation = new LLVector3(128, 128, 100);
63 user.UUID = LLUUID.Random();
64 user.username = firstName;
65 user.surname = lastName;
66 user.passwordHash = pass;
67 user.passwordSalt = "";
68 user.created = Util.UnixTimeSinceEpoch();
69 user.homeLookAt = new LLVector3(100, 100, 100);
70 user.homeRegion = Util.UIntsToLong((regX * 256), (regY * 256));
71
72 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
73 {
74 try
75 {
76 plugin.Value.addNewUserProfile(user);
77
78 }
79 catch (Exception e)
80 {
81 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
82 }
83 }
84 }
85
86 /// <summary>
87 /// Loads a user profile from a database by UUID
88 /// </summary>
89 /// <param name="uuid">The target UUID</param>
90 /// <returns>A user profile</returns>
91 public UserProfileData getUserProfile(LLUUID uuid)
92 {
93 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
94 {
95 try
96 {
97 UserProfileData profile = plugin.Value.getUserByUUID(uuid);
98 profile.currentAgent = getUserAgent(profile.UUID);
99 return profile;
100 }
101 catch (Exception e)
102 {
103 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
104 }
105 }
106
107 return null;
108 }
109
110
111 /// <summary>
112 /// Loads a user profile by name
113 /// </summary>
114 /// <param name="name">The target name</param>
115 /// <returns>A user profile</returns>
116 public UserProfileData getUserProfile(string name)
117 {
118 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
119 {
120 try
121 {
122 UserProfileData profile = plugin.Value.getUserByName(name);
123 profile.currentAgent = getUserAgent(profile.UUID);
124 return profile;
125 }
126 catch (Exception e)
127 {
128 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
129 }
130 }
131
132 return null;
133 }
134
135 /// <summary>
136 /// Loads a user profile by name
137 /// </summary>
138 /// <param name="fname">First name</param>
139 /// <param name="lname">Last name</param>
140 /// <returns>A user profile</returns>
141 public UserProfileData getUserProfile(string fname, string lname)
142 {
143 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
144 {
145 try
146 {
147 UserProfileData profile = plugin.Value.getUserByName(fname,lname);
148 try
149 {
150 profile.currentAgent = getUserAgent(profile.UUID);
151 }
152 catch (Exception e)
153 {
154 // Ignore
155 }
156 return profile;
157 }
158 catch (Exception e)
159 {
160 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
161 }
162 }
163
164 return null;
165 }
166
167 /// <summary>
168 /// Loads a user agent by uuid (not called directly)
169 /// </summary>
170 /// <param name="uuid">The agents UUID</param>
171 /// <returns>Agent profiles</returns>
172 public UserAgentData getUserAgent(LLUUID uuid)
173 {
174 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
175 {
176 try
177 {
178 return plugin.Value.getAgentByUUID(uuid);
179 }
180 catch (Exception e)
181 {
182 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
183 }
184 }
185
186 return null;
187 }
188
189 /// <summary>
190 /// Loads a user agent by name (not called directly)
191 /// </summary>
192 /// <param name="name">The agents name</param>
193 /// <returns>A user agent</returns>
194 public UserAgentData getUserAgent(string name)
195 {
196 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
197 {
198 try
199 {
200 return plugin.Value.getAgentByName(name);
201 }
202 catch (Exception e)
203 {
204 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
205 }
206 }
207
208 return null;
209 }
210
211 /// <summary>
212 /// Loads a user agent by name (not called directly)
213 /// </summary>
214 /// <param name="fname">The agents firstname</param>
215 /// <param name="lname">The agents lastname</param>
216 /// <returns>A user agent</returns>
217 public UserAgentData getUserAgent(string fname, string lname)
218 {
219 foreach (KeyValuePair<string, IUserData> plugin in _plugins)
220 {
221 try
222 {
223 return plugin.Value.getAgentByName(fname,lname);
224 }
225 catch (Exception e)
226 {
227 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(OpenSim.Framework.Console.LogPriority.LOW, "Unable to find user via " + plugin.Key + "(" + e.ToString() + ")");
228 }
229 }
230
231 return null;
232 }
233
234 /// <summary>
235 /// Creates a error response caused by invalid XML
236 /// </summary>
237 /// <returns>An XMLRPC response</returns>
238 private static XmlRpcResponse CreateErrorConnectingToGridResponse()
239 {
240 XmlRpcResponse response = new XmlRpcResponse();
241 Hashtable ErrorRespData = new Hashtable();
242 ErrorRespData["reason"] = "key";
243 ErrorRespData["message"] = "Error connecting to grid. Could not percieve credentials from login XML.";
244 ErrorRespData["login"] = "false";
245 response.Value = ErrorRespData;
246 return response;
247 }
248
249 /// <summary>
250 /// Creates an error response caused by bad login credentials
251 /// </summary>
252 /// <returns>An XMLRPC response</returns>
253 private static XmlRpcResponse CreateLoginErrorResponse()
254 {
255 XmlRpcResponse response = new XmlRpcResponse();
256 Hashtable ErrorRespData = new Hashtable();
257 ErrorRespData["reason"] = "key";
258 ErrorRespData["message"] = "Could not authenticate your avatar. Please check your username and password, and check the grid if problems persist.";
259 ErrorRespData["login"] = "false";
260 response.Value = ErrorRespData;
261 return response;
262 }
263
264 /// <summary>
265 /// Creates an error response caused by being logged in already
266 /// </summary>
267 /// <returns>An XMLRPC Response</returns>
268 private static XmlRpcResponse CreateAlreadyLoggedInResponse()
269 {
270 XmlRpcResponse response = new XmlRpcResponse();
271 Hashtable PresenceErrorRespData = new Hashtable();
272 PresenceErrorRespData["reason"] = "presence";
273 PresenceErrorRespData["message"] = "You appear to be already logged in, if this is not the case please wait for your session to timeout, if this takes longer than a few minutes please contact the grid owner";
274 PresenceErrorRespData["login"] = "false";
275 response.Value = PresenceErrorRespData;
276 return response;
277 }
278
279 /// <summary>
280 /// Customises the login response and fills in missing values.
281 /// </summary>
282 /// <param name="response">The existing response</param>
283 /// <param name="theUser">The user profile</param>
284 public virtual void CustomiseResponse(ref Hashtable response, ref UserProfileData theUser)
285 {
286 // Load information from the gridserver
287 SimProfile SimInfo = new SimProfile();
288 SimInfo = SimInfo.LoadFromGrid(theUser.currentAgent.currentHandle, _config.GridServerURL, _config.GridSendKey, _config.GridRecvKey);
289
290 // Customise the response
291 // Home Location
292 response["home"] = "{'region_handle':[r" + (SimInfo.RegionLocX * 256).ToString() + ",r" + (SimInfo.RegionLocY * 256).ToString() + "], " +
293 "'position':[r" + theUser.homeLocation.X.ToString() + ",r" + theUser.homeLocation.Y.ToString() + ",r" + theUser.homeLocation.Z.ToString() + "], " +
294 "'look_at':[r" + theUser.homeLocation.X.ToString() + ",r" + theUser.homeLocation.Y.ToString() + ",r" + theUser.homeLocation.Z.ToString() + "]}";
295
296 // Destination
297 response["sim_ip"] = SimInfo.sim_ip;
298 response["sim_port"] = (Int32)SimInfo.sim_port;
299 response["region_y"] = (Int32)SimInfo.RegionLocY * 256;
300 response["region_x"] = (Int32)SimInfo.RegionLocX * 256;
301
302 // Notify the target of an incoming user
303 Console.WriteLine("Notifying " + SimInfo.regionname + " (" + SimInfo.caps_url + ")");
304
305 // Prepare notification
306 Hashtable SimParams = new Hashtable();
307 SimParams["session_id"] = theUser.currentAgent.sessionID.ToString();
308 SimParams["secure_session_id"] = theUser.currentAgent.secureSessionID.ToString();
309 SimParams["firstname"] = theUser.username;
310 SimParams["lastname"] = theUser.surname;
311 SimParams["agent_id"] = theUser.UUID.ToString();
312 SimParams["circuit_code"] = (Int32)Convert.ToUInt32(response["circuit_code"]);
313 SimParams["startpos_x"] = theUser.currentAgent.currentPos.X.ToString();
314 SimParams["startpos_y"] = theUser.currentAgent.currentPos.Y.ToString();
315 SimParams["startpos_z"] = theUser.currentAgent.currentPos.Z.ToString();
316 ArrayList SendParams = new ArrayList();
317 SendParams.Add(SimParams);
318
319 // Update agent with target sim
320 theUser.currentAgent.currentRegion = SimInfo.UUID;
321 theUser.currentAgent.currentHandle = SimInfo.regionhandle;
322
323 // Send
324 XmlRpcRequest GridReq = new XmlRpcRequest("expect_user", SendParams);
325 XmlRpcResponse GridResp = GridReq.Send(SimInfo.caps_url, 3000);
326 }
327
328 /// <summary>
329 /// Checks a user against it's password hash
330 /// </summary>
331 /// <param name="profile">The users profile</param>
332 /// <param name="password">The supplied password</param>
333 /// <returns>Authenticated?</returns>
334 public bool AuthenticateUser(ref UserProfileData profile, string password)
335 {
336 OpenSim.Framework.Console.MainConsole.Instance.WriteLine(
337 OpenSim.Framework.Console.LogPriority.LOW,
338 "Authenticating " + profile.username + " " + profile.surname);
339
340 password = password.Remove(0, 3); //remove $1$
341
342 string s = Util.Md5Hash(password + ":" + profile.passwordSalt);
343
344 return profile.passwordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase);
345 }
346
347 /// <summary>
348 /// Creates and initialises a new user agent - make sure to use CommitAgent when done to submit to the DB
349 /// </summary>
350 /// <param name="profile">The users profile</param>
351 /// <param name="request">The users loginrequest</param>
352 public void CreateAgent(ref UserProfileData profile, XmlRpcRequest request)
353 {
354 Hashtable requestData = (Hashtable)request.Params[0];
355
356 UserAgentData agent = new UserAgentData();
357
358 // User connection
359 agent.agentIP = "";
360 agent.agentOnline = true;
361 agent.agentPort = 0;
362
363 // Generate sessions
364 RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider();
365 byte[] randDataS = new byte[16];
366 byte[] randDataSS = new byte[16];
367 rand.GetBytes(randDataS);
368 rand.GetBytes(randDataSS);
369
370 agent.secureSessionID = new LLUUID(randDataSS, 0);
371 agent.sessionID = new LLUUID(randDataS, 0);
372
373 // Profile UUID
374 agent.UUID = profile.UUID;
375
376 // Current position (from Home)
377 agent.currentHandle = profile.homeRegion;
378 agent.currentPos = profile.homeLocation;
379
380 // If user specified additional start, use that
381 if (requestData.ContainsKey("start"))
382 {
383 string startLoc = (string)requestData["start"];
384 if (!(startLoc == "last" || startLoc == "home"))
385 {
386 // Ignore it! Heh.
387 }
388 }
389
390 // What time did the user login?
391 agent.loginTime = Util.UnixTimeSinceEpoch();
392 agent.logoutTime = 0;
393
394 // Current location
395 agent.regionID = new LLUUID(); // Fill in later
396 agent.currentRegion = new LLUUID(); // Fill in later
397
398 profile.currentAgent = agent;
399 }
400
401 /// <summary>
402 /// Saves a target agent to the database
403 /// </summary>
404 /// <param name="profile">The users profile</param>
405 /// <returns>Successful?</returns>
406 public bool CommitAgent(ref UserProfileData profile)
407 {
408 // Saves the agent to database
409 return true;
410 }
411
412 /// <summary>
413 /// Main user login function
414 /// </summary>
415 /// <param name="request">The XMLRPC request</param>
416 /// <returns>The response to send</returns>
417 public XmlRpcResponse XmlRpcLoginMethod(XmlRpcRequest request)
418 {
419 XmlRpcResponse response = new XmlRpcResponse();
420 Hashtable requestData = (Hashtable)request.Params[0];
421
422 bool GoodXML = (requestData.Contains("first") && requestData.Contains("last") && requestData.Contains("passwd"));
423 bool GoodLogin = false;
424 string firstname = "";
425 string lastname = "";
426 string passwd = "";
427
428 UserProfileData TheUser;
429
430 if (GoodXML)
431 {
432 firstname = (string)requestData["first"];
433 lastname = (string)requestData["last"];
434 passwd = (string)requestData["passwd"];
435
436 TheUser = getUserProfile(firstname, lastname);
437 if (TheUser == null)
438 return CreateLoginErrorResponse();
439
440 GoodLogin = AuthenticateUser(ref TheUser, passwd);
441 }
442 else
443 {
444 return CreateErrorConnectingToGridResponse();
445 }
446
447 if (!GoodLogin)
448 {
449 return CreateLoginErrorResponse();
450 }
451 else
452 {
453 // If we already have a session...
454 if (TheUser.currentAgent != null && TheUser.currentAgent.agentOnline)
455 {
456 // Reject the login
457 return CreateAlreadyLoggedInResponse();
458 }
459 // Otherwise...
460 // Create a new agent session
461 CreateAgent(ref TheUser, request);
462
463 try
464 {
465 Hashtable responseData = new Hashtable();
466
467 LLUUID AgentID = TheUser.UUID;
468
469 // Global Texture Section
470 Hashtable GlobalT = new Hashtable();
471 GlobalT["sun_texture_id"] = "cce0f112-878f-4586-a2e2-a8f104bba271";
472 GlobalT["cloud_texture_id"] = "fc4b9f0b-d008-45c6-96a4-01dd947ac621";
473 GlobalT["moon_texture_id"] = "fc4b9f0b-d008-45c6-96a4-01dd947ac621";
474 ArrayList GlobalTextures = new ArrayList();
475 GlobalTextures.Add(GlobalT);
476
477 // Login Flags Section
478 Hashtable LoginFlagsHash = new Hashtable();
479 LoginFlagsHash["daylight_savings"] = "N";
480 LoginFlagsHash["stipend_since_login"] = "N";
481 LoginFlagsHash["gendered"] = "Y";
482 LoginFlagsHash["ever_logged_in"] = "N"; // Should allow male/female av selection
483 ArrayList LoginFlags = new ArrayList();
484 LoginFlags.Add(LoginFlagsHash);
485
486 // UI Customisation Section
487 Hashtable uiconfig = new Hashtable();
488 uiconfig["allow_first_life"] = "Y";
489 ArrayList ui_config = new ArrayList();
490 ui_config.Add(uiconfig);
491
492 // Classified Categories Section
493 Hashtable ClassifiedCategoriesHash = new Hashtable();
494 ClassifiedCategoriesHash["category_name"] = "Generic";
495 ClassifiedCategoriesHash["category_id"] = (Int32)1;
496 ArrayList ClassifiedCategories = new ArrayList();
497 ClassifiedCategories.Add(ClassifiedCategoriesHash);
498
499 // Inventory Library Section
500 ArrayList AgentInventoryArray = new ArrayList();
501 Hashtable TempHash;
502
503 AgentInventory Library = new AgentInventory();
504 Library.CreateRootFolder(AgentID, true);
505
506 foreach (InventoryFolder InvFolder in Library.InventoryFolders.Values)
507 {
508 TempHash = new Hashtable();
509 TempHash["name"] = InvFolder.FolderName;
510 TempHash["parent_id"] = InvFolder.ParentID.ToStringHyphenated();
511 TempHash["version"] = (Int32)InvFolder.Version;
512 TempHash["type_default"] = (Int32)InvFolder.DefaultType;
513 TempHash["folder_id"] = InvFolder.FolderID.ToStringHyphenated();
514 AgentInventoryArray.Add(TempHash);
515 }
516
517 Hashtable InventoryRootHash = new Hashtable();
518 InventoryRootHash["folder_id"] = Library.InventoryRoot.FolderID.ToStringHyphenated();
519 ArrayList InventoryRoot = new ArrayList();
520 InventoryRoot.Add(InventoryRootHash);
521
522 Hashtable InitialOutfitHash = new Hashtable();
523 InitialOutfitHash["folder_name"] = "Nightclub Female";
524 InitialOutfitHash["gender"] = "female";
525 ArrayList InitialOutfit = new ArrayList();
526 InitialOutfit.Add(InitialOutfitHash);
527
528 // Circuit Code
529 uint circode = (uint)(Util.RandomClass.Next());
530
531 // Generics
532 responseData["last_name"] = TheUser.surname;
533 responseData["ui-config"] = ui_config;
534 responseData["sim_ip"] = "127.0.0.1"; //SimInfo.sim_ip.ToString();
535 responseData["login-flags"] = LoginFlags;
536 responseData["global-textures"] = GlobalTextures;
537 responseData["classified_categories"] = ClassifiedCategories;
538 responseData["event_categories"] = new ArrayList();
539 responseData["inventory-skeleton"] = AgentInventoryArray;
540 responseData["inventory-skel-lib"] = new ArrayList();
541 responseData["inventory-root"] = InventoryRoot;
542 responseData["event_notifications"] = new ArrayList();
543 responseData["gestures"] = new ArrayList();
544 responseData["inventory-lib-owner"] = new ArrayList();
545 responseData["initial-outfit"] = InitialOutfit;
546 responseData["seconds_since_epoch"] = (Int32)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
547 responseData["start_location"] = "last";
548 responseData["home"] = "!!null temporary value {home}!!"; // Overwritten
549 responseData["message"] = _config.DefaultStartupMsg;
550 responseData["first_name"] = TheUser.username;
551 responseData["circuit_code"] = (Int32)circode;
552 responseData["sim_port"] = 0; //(Int32)SimInfo.sim_port;
553 responseData["secure_session_id"] = TheUser.currentAgent.secureSessionID.ToStringHyphenated();
554 responseData["look_at"] = "\n[r" + TheUser.homeLookAt.X.ToString() + ",r" + TheUser.homeLookAt.Y.ToString() + ",r" + TheUser.homeLookAt.Z.ToString() + "]\n";
555 responseData["agent_id"] = AgentID.ToStringHyphenated();
556 responseData["region_y"] = (Int32)0; // Overwritten
557 responseData["region_x"] = (Int32)0; // Overwritten
558 responseData["seed_capability"] = "";
559 responseData["agent_access"] = "M";
560 responseData["session_id"] = TheUser.currentAgent.sessionID.ToStringHyphenated();
561 responseData["login"] = "true";
562
563 this.CustomiseResponse(ref responseData, ref TheUser);
564
565 CommitAgent(ref TheUser);
566
567 response.Value = responseData;
568 // TheUser.SendDataToSim(SimInfo);
569 return response;
570
571 }
572 catch (Exception E)
573 {
574 Console.WriteLine(E.ToString());
575 }
576 //}
577 }
578 return response;
579
580 }
581
582 /// <summary>
583 /// Deletes an active agent session
584 /// </summary>
585 /// <param name="request">The request</param>
586 /// <param name="path">The path (eg /bork/narf/test)</param>
587 /// <param name="param">Parameters sent</param>
588 /// <returns>Success "OK" else error</returns>
589 public string RestDeleteUserSessionMethod(string request, string path, string param)
590 {
591 // TODO! Important!
592
593 return "OK";
594 }
595
596 }
597}