aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs96
1 files changed, 48 insertions, 48 deletions
diff --git a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs
index e540e4d..c436596 100644
--- a/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs
+++ b/OpenSim/ApplicationPlugins/RemoteController/RemoteAdminPlugin.cs
@@ -58,7 +58,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
58 { 58 {
59 try 59 try
60 { 60 {
61 if (openSim.ConfigSource.Configs["RemoteAdmin"] != null && 61 if (openSim.ConfigSource.Configs["RemoteAdmin"] != null &&
62 openSim.ConfigSource.Configs["RemoteAdmin"].GetBoolean("enabled", false)) 62 openSim.ConfigSource.Configs["RemoteAdmin"].GetBoolean("enabled", false))
63 { 63 {
64 m_log.Info("[RADMIN]: Remote Admin Plugin Enabled"); 64 m_log.Info("[RADMIN]: Remote Admin Plugin Enabled");
@@ -96,7 +96,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
96 if (requiredPassword != String.Empty && 96 if (requiredPassword != String.Empty &&
97 (!requestData.Contains("password") || (string) requestData["password"] != requiredPassword)) 97 (!requestData.Contains("password") || (string) requestData["password"] != requiredPassword))
98 throw new Exception("wrong password"); 98 throw new Exception("wrong password");
99 99
100 LLUUID regionID = new LLUUID((string) requestData["regionID"]); 100 LLUUID regionID = new LLUUID((string) requestData["regionID"]);
101 101
102 responseData["accepted"] = "true"; 102 responseData["accepted"] = "true";
@@ -106,10 +106,10 @@ namespace OpenSim.ApplicationPlugins.RemoteController
106 106
107 if (!m_app.SceneManager.TryGetScene(regionID, out rebootedScene)) 107 if (!m_app.SceneManager.TryGetScene(regionID, out rebootedScene))
108 throw new Exception("region not found"); 108 throw new Exception("region not found");
109 109
110 responseData["rebooting"] = "true"; 110 responseData["rebooting"] = "true";
111 rebootedScene.Restart(30); 111 rebootedScene.Restart(30);
112 } 112 }
113 catch(Exception e) 113 catch(Exception e)
114 { 114 {
115 m_log.ErrorFormat("[RADMIN]: Restart region: failed: {0}", e.Message); 115 m_log.ErrorFormat("[RADMIN]: Restart region: failed: {0}", e.Message);
@@ -136,7 +136,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
136 if (requiredPassword != String.Empty && 136 if (requiredPassword != String.Empty &&
137 (!requestData.Contains("password") || (string) requestData["password"] != requiredPassword)) 137 (!requestData.Contains("password") || (string) requestData["password"] != requiredPassword))
138 throw new Exception("wrong password"); 138 throw new Exception("wrong password");
139 139
140 string message = (string) requestData["message"]; 140 string message = (string) requestData["message"];
141 m_log.InfoFormat("[RADMIN]: Broadcasting: {0}", message); 141 m_log.InfoFormat("[RADMIN]: Broadcasting: {0}", message);
142 142
@@ -167,7 +167,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
167 m_log.DebugFormat("[RADMIN]: Load Terrain: XmlRpc {0}", request.ToString()); 167 m_log.DebugFormat("[RADMIN]: Load Terrain: XmlRpc {0}", request.ToString());
168 foreach (string k in requestData.Keys) 168 foreach (string k in requestData.Keys)
169 { 169 {
170 m_log.DebugFormat("[RADMIN]: Load Terrain: XmlRpc {0}: >{1}< {2}", 170 m_log.DebugFormat("[RADMIN]: Load Terrain: XmlRpc {0}: >{1}< {2}",
171 k, (string)requestData[k], ((string)requestData[k]).Length); 171 k, (string)requestData[k], ((string)requestData[k]).Length);
172 } 172 }
173 173
@@ -198,7 +198,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
198 198
199 response.Value = responseData; 199 response.Value = responseData;
200 } 200 }
201 catch (Exception e) 201 catch (Exception e)
202 { 202 {
203 m_log.ErrorFormat("[RADMIN] Terrain Loading: failed: {0}", e.Message); 203 m_log.ErrorFormat("[RADMIN] Terrain Loading: failed: {0}", e.Message);
204 m_log.DebugFormat("[RADMIN] Terrain Loading: failed: {0}", e.ToString()); 204 m_log.DebugFormat("[RADMIN] Terrain Loading: failed: {0}", e.ToString());
@@ -227,14 +227,14 @@ namespace OpenSim.ApplicationPlugins.RemoteController
227 227
228 int timeout = 2000; 228 int timeout = 2000;
229 229
230 if (requestData.ContainsKey("shutdown") && 230 if (requestData.ContainsKey("shutdown") &&
231 ((string) requestData["shutdown"] == "delayed") && 231 ((string) requestData["shutdown"] == "delayed") &&
232 requestData.ContainsKey("milliseconds")) 232 requestData.ContainsKey("milliseconds"))
233 { 233 {
234 timeout = (Int32) requestData["milliseconds"]; 234 timeout = (Int32) requestData["milliseconds"];
235 m_app.SceneManager.SendGeneralMessage("Region is going down in " + ((int) (timeout/1000)).ToString() + 235 m_app.SceneManager.SendGeneralMessage("Region is going down in " + ((int) (timeout/1000)).ToString() +
236 " second(s). Please save what you are doing and log out."); 236 " second(s). Please save what you are doing and log out.");
237 } 237 }
238 else 238 else
239 { 239 {
240 m_app.SceneManager.SendGeneralMessage("Region is going down now."); 240 m_app.SceneManager.SendGeneralMessage("Region is going down now.");
@@ -248,7 +248,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
248 248
249 responseData["success"] = "true"; 249 responseData["success"] = "true";
250 } 250 }
251 catch (Exception e) 251 catch (Exception e)
252 { 252 {
253 m_log.ErrorFormat("[RADMIN] Shutdown: failed: {0}", e.Message); 253 m_log.ErrorFormat("[RADMIN] Shutdown: failed: {0}", e.Message);
254 m_log.DebugFormat("[RADMIN] Shutdown: failed: {0}", e.ToString()); 254 m_log.DebugFormat("[RADMIN] Shutdown: failed: {0}", e.ToString());
@@ -272,7 +272,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
272 Hashtable requestData = (Hashtable) request.Params[0]; 272 Hashtable requestData = (Hashtable) request.Params[0];
273 foreach (string p in param) 273 foreach (string p in param)
274 { 274 {
275 if (!requestData.Contains(p)) 275 if (!requestData.Contains(p))
276 throw new Exception(String.Format("missing string parameter {0}", p)); 276 throw new Exception(String.Format("missing string parameter {0}", p));
277 if (String.IsNullOrEmpty((string)requestData[p])) 277 if (String.IsNullOrEmpty((string)requestData[p]))
278 throw new Exception(String.Format("parameter {0} is empty", p)); 278 throw new Exception(String.Format("parameter {0} is empty", p));
@@ -284,7 +284,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
284 Hashtable requestData = (Hashtable) request.Params[0]; 284 Hashtable requestData = (Hashtable) request.Params[0];
285 foreach (string p in param) 285 foreach (string p in param)
286 { 286 {
287 if (!requestData.Contains(p)) 287 if (!requestData.Contains(p))
288 throw new Exception(String.Format("missing integer parameter {0}", p)); 288 throw new Exception(String.Format("missing integer parameter {0}", p));
289 } 289 }
290 } 290 }
@@ -324,7 +324,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
324 /// <description>if true, persist the region info 324 /// <description>if true, persist the region info
325 /// ('true' or 'false')</description></item> 325 /// ('true' or 'false')</description></item>
326 /// </list> 326 /// </list>
327 /// 327 ///
328 /// XmlRpcCreateRegionMethod returns 328 /// XmlRpcCreateRegionMethod returns
329 /// <list type="table"> 329 /// <list type="table">
330 /// <listheader><term>name</term><description>description</description></listheader> 330 /// <listheader><term>name</term><description>description</description></listheader>
@@ -346,9 +346,9 @@ namespace OpenSim.ApplicationPlugins.RemoteController
346 Hashtable responseData = new Hashtable(); 346 Hashtable responseData = new Hashtable();
347 347
348 try { 348 try {
349 checkStringParameters(request, new string[] { "password", 349 checkStringParameters(request, new string[] { "password",
350 "region_name", 350 "region_name",
351 "region_master_first", "region_master_last", 351 "region_master_first", "region_master_last",
352 "region_master_password", 352 "region_master_password",
353 "listen_ip", "external_address"}); 353 "listen_ip", "external_address"});
354 checkIntegerParams(request, new string[] { "region_x", "region_y", "listen_port"}); 354 checkIntegerParams(request, new string[] { "region_x", "region_y", "listen_port"});
@@ -360,16 +360,16 @@ namespace OpenSim.ApplicationPlugins.RemoteController
360 // extract or generate region ID now 360 // extract or generate region ID now
361 Scene scene = null; 361 Scene scene = null;
362 LLUUID regionID = LLUUID.Zero; 362 LLUUID regionID = LLUUID.Zero;
363 if (requestData.ContainsKey("region_id") && 363 if (requestData.ContainsKey("region_id") &&
364 !String.IsNullOrEmpty((string)requestData["region_id"])) 364 !String.IsNullOrEmpty((string)requestData["region_id"]))
365 { 365 {
366 regionID = (string) requestData["region_id"]; 366 regionID = (string) requestData["region_id"];
367 if (m_app.SceneManager.TryGetScene(regionID, out scene)) 367 if (m_app.SceneManager.TryGetScene(regionID, out scene))
368 throw new Exception(String.Format("region UUID already in use by region {0}, UUID {1}, <{2},{3}>", 368 throw new Exception(String.Format("region UUID already in use by region {0}, UUID {1}, <{2},{3}>",
369 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID, 369 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID,
370 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY)); 370 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY));
371 } 371 }
372 else 372 else
373 { 373 {
374 regionID = LLUUID.Random(); 374 regionID = LLUUID.Random();
375 m_log.DebugFormat("[RADMIN] CreateRegion: new region UUID {0}", regionID); 375 m_log.DebugFormat("[RADMIN] CreateRegion: new region UUID {0}", regionID);
@@ -385,7 +385,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
385 385
386 // check for collisions: region name, region UUID, 386 // check for collisions: region name, region UUID,
387 // region location 387 // region location
388 if (m_app.SceneManager.TryGetScene(region.RegionName, out scene)) 388 if (m_app.SceneManager.TryGetScene(region.RegionName, out scene))
389 throw new Exception(String.Format("region name already in use by region {0}, UUID {1}, <{2},{3}>", 389 throw new Exception(String.Format("region name already in use by region {0}, UUID {1}, <{2},{3}>",
390 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID, 390 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID,
391 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY)); 391 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY));
@@ -395,43 +395,43 @@ namespace OpenSim.ApplicationPlugins.RemoteController
395 region.RegionLocX, region.RegionLocY, 395 region.RegionLocX, region.RegionLocY,
396 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID, 396 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID,
397 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY)); 397 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY));
398 398
399 // Security risk [and apparently not used] 399 // Security risk [and apparently not used]
400 // if (requestData.ContainsKey("datastore")) 400 // if (requestData.ContainsKey("datastore"))
401 // region.DataStore = (string) requestData["datastore"]; 401 // region.DataStore = (string) requestData["datastore"];
402 402
403 region.InternalEndPoint = 403 region.InternalEndPoint =
404 new IPEndPoint(IPAddress.Parse((string) requestData["listen_ip"]), 0); 404 new IPEndPoint(IPAddress.Parse((string) requestData["listen_ip"]), 0);
405 405
406 region.InternalEndPoint.Port = (Int32) requestData["listen_port"]; 406 region.InternalEndPoint.Port = (Int32) requestData["listen_port"];
407 if (0 == region.InternalEndPoint.Port) throw new Exception("listen_port is 0"); 407 if (0 == region.InternalEndPoint.Port) throw new Exception("listen_port is 0");
408 if (m_app.SceneManager.TryGetScene(region.InternalEndPoint, out scene)) 408 if (m_app.SceneManager.TryGetScene(region.InternalEndPoint, out scene))
409 throw new Exception(String.Format("region internal IP {0} and port {1} already in use by region {2}, UUID {3}, <{4},{5}>", 409 throw new Exception(String.Format("region internal IP {0} and port {1} already in use by region {2}, UUID {3}, <{4},{5}>",
410 region.InternalEndPoint.Address, 410 region.InternalEndPoint.Address,
411 region.InternalEndPoint.Port, 411 region.InternalEndPoint.Port,
412 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID, 412 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID,
413 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY)); 413 scene.RegionInfo.RegionLocX, scene.RegionInfo.RegionLocY));
414 414
415 415
416 region.ExternalHostName = (string) requestData["external_address"]; 416 region.ExternalHostName = (string) requestData["external_address"];
417 417
418 region.MasterAvatarFirstName = (string) requestData["region_master_first"]; 418 region.MasterAvatarFirstName = (string) requestData["region_master_first"];
419 region.MasterAvatarLastName = (string) requestData["region_master_last"]; 419 region.MasterAvatarLastName = (string) requestData["region_master_last"];
420 region.MasterAvatarSandboxPassword = (string) requestData["region_master_password"]; 420 region.MasterAvatarSandboxPassword = (string) requestData["region_master_password"];
421 421
422 bool persist = Convert.ToBoolean((string)requestData["persist"]); 422 bool persist = Convert.ToBoolean((string)requestData["persist"]);
423 if (persist) 423 if (persist)
424 { 424 {
425 string regionConfigPath = Path.Combine(Path.Combine(Util.configDir(), "Regions"), 425 string regionConfigPath = Path.Combine(Path.Combine(Util.configDir(), "Regions"),
426 String.Format("{0}x{1}-{2}.xml", 426 String.Format("{0}x{1}-{2}.xml",
427 region.RegionLocX.ToString(), 427 region.RegionLocX.ToString(),
428 region.RegionLocY.ToString(), 428 region.RegionLocY.ToString(),
429 regionID.ToString())); 429 regionID.ToString()));
430 m_log.DebugFormat("[RADMIN] CreateRegion: persisting region {0} to {1}", 430 m_log.DebugFormat("[RADMIN] CreateRegion: persisting region {0} to {1}",
431 region.RegionID, regionConfigPath); 431 region.RegionID, regionConfigPath);
432 region.SaveRegionToFile("dynamic region", regionConfigPath); 432 region.SaveRegionToFile("dynamic region", regionConfigPath);
433 } 433 }
434 434
435 m_app.CreateRegion(region); 435 m_app.CreateRegion(region);
436 436
437 responseData["success"] = "true"; 437 responseData["success"] = "true";
@@ -476,7 +476,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
476 /// <item><term>start_region_y</term> 476 /// <item><term>start_region_y</term>
477 /// <description>avatar's start region coordinates, Y value</description></item> 477 /// <description>avatar's start region coordinates, Y value</description></item>
478 /// </list> 478 /// </list>
479 /// 479 ///
480 /// XmlRpcCreateUserMethod returns 480 /// XmlRpcCreateUserMethod returns
481 /// <list type="table"> 481 /// <list type="table">
482 /// <listheader><term>name</term><description>description</description></listheader> 482 /// <listheader><term>name</term><description>description</description></listheader>
@@ -497,10 +497,10 @@ namespace OpenSim.ApplicationPlugins.RemoteController
497 Hashtable requestData = (Hashtable) request.Params[0]; 497 Hashtable requestData = (Hashtable) request.Params[0];
498 Hashtable responseData = new Hashtable(); 498 Hashtable responseData = new Hashtable();
499 499
500 try 500 try
501 { 501 {
502 // check completeness 502 // check completeness
503 checkStringParameters(request, new string[] { "password", "user_firstname", 503 checkStringParameters(request, new string[] { "password", "user_firstname",
504 "user_lastname", "user_password" }); 504 "user_lastname", "user_password" });
505 checkIntegerParams(request, new string[] { "start_region_x", "start_region_y" }); 505 checkIntegerParams(request, new string[] { "start_region_x", "start_region_y" });
506 506
@@ -514,16 +514,16 @@ namespace OpenSim.ApplicationPlugins.RemoteController
514 string passwd = (string) requestData["user_password"]; 514 string passwd = (string) requestData["user_password"];
515 uint regX = Convert.ToUInt32((Int32)requestData["start_region_x"]); 515 uint regX = Convert.ToUInt32((Int32)requestData["start_region_x"]);
516 uint regY = Convert.ToUInt32((Int32)requestData["start_region_y"]); 516 uint regY = Convert.ToUInt32((Int32)requestData["start_region_y"]);
517 517
518 UserProfileData userProfile = m_app.CommunicationsManager.UserService.GetUserProfile(firstname, lastname); 518 UserProfileData userProfile = m_app.CommunicationsManager.UserService.GetUserProfile(firstname, lastname);
519 if (null != userProfile) 519 if (null != userProfile)
520 throw new Exception(String.Format("avatar {0} {1} already exists", firstname, lastname)); 520 throw new Exception(String.Format("avatar {0} {1} already exists", firstname, lastname));
521 521
522 LLUUID userID = m_app.CreateUser(firstname, lastname, passwd, regX, regY); 522 LLUUID userID = m_app.CreateUser(firstname, lastname, passwd, regX, regY);
523 523
524 if (userID == LLUUID.Zero) throw new Exception(String.Format("failed to create new user {0} {1}", 524 if (userID == LLUUID.Zero) throw new Exception(String.Format("failed to create new user {0} {1}",
525 firstname, lastname)); 525 firstname, lastname));
526 526
527 responseData["success"] = "true"; 527 responseData["success"] = "true";
528 responseData["avatar_uuid"] = userID.ToString(); 528 responseData["avatar_uuid"] = userID.ToString();
529 529
@@ -531,7 +531,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
531 531
532 m_log.InfoFormat("[RADMIN]: CreateUser: User {0} {1} created, UUID {2}", firstname, lastname, userID); 532 m_log.InfoFormat("[RADMIN]: CreateUser: User {0} {1} created, UUID {2}", firstname, lastname, userID);
533 } 533 }
534 catch (Exception e) 534 catch (Exception e)
535 { 535 {
536 m_log.ErrorFormat("[RADMIN] CreateUser: failed: {0}", e.Message); 536 m_log.ErrorFormat("[RADMIN] CreateUser: failed: {0}", e.Message);
537 m_log.DebugFormat("[RADMIN] CreateUser: failed: {0}", e.ToString()); 537 m_log.DebugFormat("[RADMIN] CreateUser: failed: {0}", e.ToString());
@@ -553,33 +553,33 @@ namespace OpenSim.ApplicationPlugins.RemoteController
553 Hashtable requestData = (Hashtable) request.Params[0]; 553 Hashtable requestData = (Hashtable) request.Params[0];
554 Hashtable responseData = new Hashtable(); 554 Hashtable responseData = new Hashtable();
555 555
556 try 556 try
557 { 557 {
558 // check completeness 558 // check completeness
559 foreach (string p in new string[] { "password", "filename" }) 559 foreach (string p in new string[] { "password", "filename" })
560 { 560 {
561 if (!requestData.Contains(p)) 561 if (!requestData.Contains(p))
562 throw new Exception(String.Format("missing parameter {0}", p)); 562 throw new Exception(String.Format("missing parameter {0}", p));
563 if (String.IsNullOrEmpty((string)requestData[p])) 563 if (String.IsNullOrEmpty((string)requestData[p]))
564 throw new Exception(String.Format("parameter {0} is empty")); 564 throw new Exception(String.Format("parameter {0} is empty"));
565 } 565 }
566 566
567 // check password 567 // check password
568 if (!String.IsNullOrEmpty(requiredPassword) && 568 if (!String.IsNullOrEmpty(requiredPassword) &&
569 (string)requestData["password"] != requiredPassword) throw new Exception("wrong password"); 569 (string)requestData["password"] != requiredPassword) throw new Exception("wrong password");
570 570
571 string filename = (string)requestData["filename"]; 571 string filename = (string)requestData["filename"];
572 if (requestData.Contains("region_uuid")) 572 if (requestData.Contains("region_uuid"))
573 { 573 {
574 LLUUID region_uuid = (string)requestData["region_uuid"]; 574 LLUUID region_uuid = (string)requestData["region_uuid"];
575 if (!m_app.SceneManager.TrySetCurrentScene(region_uuid)) 575 if (!m_app.SceneManager.TrySetCurrentScene(region_uuid))
576 throw new Exception(String.Format("failed to switch to region {0}", region_uuid.ToString())); 576 throw new Exception(String.Format("failed to switch to region {0}", region_uuid.ToString()));
577 m_log.InfoFormat("[RADMIN] Switched to region {0}", region_uuid.ToString()); 577 m_log.InfoFormat("[RADMIN] Switched to region {0}", region_uuid.ToString());
578 } 578 }
579 else if (requestData.Contains("region_name")) 579 else if (requestData.Contains("region_name"))
580 { 580 {
581 string region_name = (string)requestData["region_name"]; 581 string region_name = (string)requestData["region_name"];
582 if (!m_app.SceneManager.TrySetCurrentScene(region_name)) 582 if (!m_app.SceneManager.TrySetCurrentScene(region_name))
583 throw new Exception(String.Format("failed to switch to region {0}", region_name)); 583 throw new Exception(String.Format("failed to switch to region {0}", region_name));
584 m_log.InfoFormat("[RADMIN] Switched to region {0}", region_name); 584 m_log.InfoFormat("[RADMIN] Switched to region {0}", region_name);
585 } 585 }
@@ -589,7 +589,7 @@ namespace OpenSim.ApplicationPlugins.RemoteController
589 589
590 m_app.SceneManager.LoadCurrentSceneFromXml(filename, true, new LLVector3(0, 0, 0)); 590 m_app.SceneManager.LoadCurrentSceneFromXml(filename, true, new LLVector3(0, 0, 0));
591 responseData["loaded"] = "true"; 591 responseData["loaded"] = "true";
592 592
593 response.Value = responseData; 593 response.Value = responseData;
594 } 594 }
595 catch (Exception e) 595 catch (Exception e)
@@ -600,10 +600,10 @@ namespace OpenSim.ApplicationPlugins.RemoteController
600 responseData["loaded"] = "false"; 600 responseData["loaded"] = "false";
601 responseData["switched"] = "false"; 601 responseData["switched"] = "false";
602 responseData["error"] = e.Message; 602 responseData["error"] = e.Message;
603 603
604 response.Value = responseData; 604 response.Value = responseData;
605 } 605 }
606 606
607 return response; 607 return response;
608 } 608 }
609 609