diff options
author | Teravus Ovares | 2008-01-09 22:05:28 +0000 |
---|---|---|
committer | Teravus Ovares | 2008-01-09 22:05:28 +0000 |
commit | 85fe8ed0857c075ebefabbad8a670499e047f41a (patch) | |
tree | c43185637f9290a3b70c02a4c1265d9397590b27 | |
parent | * This may be broken.. it hasn't been tested, however I wanted to get the l... (diff) | |
download | opensim-SC-85fe8ed0857c075ebefabbad8a670499e047f41a.zip opensim-SC-85fe8ed0857c075ebefabbad8a670499e047f41a.tar.gz opensim-SC-85fe8ed0857c075ebefabbad8a670499e047f41a.tar.bz2 opensim-SC-85fe8ed0857c075ebefabbad8a670499e047f41a.tar.xz |
* This update enables the web_login method.
* Remember, the client doesn't support web_login to other grids in the current RC, however the next RC will.
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Framework/Communications/LoginService.cs | 148 | ||||
-rw-r--r-- | OpenSim/Framework/Servers/BaseHttpServer.cs | 7 | ||||
-rw-r--r-- | OpenSim/Grid/UserServer/Main.cs | 2 | ||||
-rw-r--r-- | OpenSim/Region/Application/OpenSimMain.cs | 6 | ||||
-rw-r--r-- | OpenSim/Region/Communications/Local/LocalLoginService.cs | 7 | ||||
-rw-r--r-- | bin/http_loginform.html.example | 60 |
6 files changed, 210 insertions, 20 deletions
diff --git a/OpenSim/Framework/Communications/LoginService.cs b/OpenSim/Framework/Communications/LoginService.cs index f0a0a0b..04b8501 100644 --- a/OpenSim/Framework/Communications/LoginService.cs +++ b/OpenSim/Framework/Communications/LoginService.cs | |||
@@ -30,6 +30,7 @@ using System; | |||
30 | using System.Collections; | 30 | using System.Collections; |
31 | using System.Collections.Generic; | 31 | using System.Collections.Generic; |
32 | using System.IO; | 32 | using System.IO; |
33 | using System.Text.RegularExpressions; | ||
33 | using System.Threading; | 34 | using System.Threading; |
34 | using libsecondlife; | 35 | using libsecondlife; |
35 | using libsecondlife.StructuredData; | 36 | using libsecondlife.StructuredData; |
@@ -359,21 +360,103 @@ namespace OpenSim.Framework.UserManagement | |||
359 | 360 | ||
360 | public Hashtable ProcessHTMLLogin(Hashtable keysvals) | 361 | public Hashtable ProcessHTMLLogin(Hashtable keysvals) |
361 | { | 362 | { |
363 | |||
364 | // Matches all unspecified characters | ||
365 | // Currently specified,; lowercase letters, upper case letters, numbers, underline | ||
366 | // period, space, parens, and dash. | ||
367 | |||
368 | Regex wfcut = new Regex("[^a-zA-Z0-9_\\.\\$ \\(\\)\\-]"); | ||
369 | |||
362 | Hashtable returnactions = new Hashtable(); | 370 | Hashtable returnactions = new Hashtable(); |
363 | int statuscode = 200; | 371 | int statuscode = 200; |
364 | 372 | ||
365 | returnactions["int_response_code"] = statuscode; | 373 | string firstname = ""; |
366 | returnactions["str_response_string"] = GetDefaultLoginForm(); | 374 | string lastname = ""; |
375 | string location = ""; | ||
376 | string region =""; | ||
377 | string grid = ""; | ||
378 | string channel = ""; | ||
379 | string version = ""; | ||
380 | string lang = ""; | ||
381 | string password = ""; | ||
382 | string errormessages = ""; | ||
383 | |||
384 | // the client requires the HTML form field be named 'username' | ||
385 | // however, the data it sends when it loads the first time is 'firstname' | ||
386 | // another one of those little nuances. | ||
387 | |||
388 | |||
389 | if (keysvals.Contains("firstname")) | ||
390 | firstname = wfcut.Replace((string)keysvals["firstname"],"",99999); | ||
391 | if (keysvals.Contains("username")) | ||
392 | firstname = wfcut.Replace((string)keysvals["username"],"",99999); | ||
393 | |||
394 | if (keysvals.Contains("lastname")) | ||
395 | lastname = wfcut.Replace((string)keysvals["lastname"],"",99999); | ||
396 | |||
397 | if (keysvals.Contains("location")) | ||
398 | location = wfcut.Replace((string)keysvals["location"],"",99999); | ||
399 | |||
400 | if (keysvals.Contains("region")) | ||
401 | region = wfcut.Replace((string)keysvals["region"],"",99999); | ||
402 | |||
403 | if (keysvals.Contains("grid")) | ||
404 | grid = wfcut.Replace((string)keysvals["grid"],"",99999); | ||
405 | |||
406 | if (keysvals.Contains("channel")) | ||
407 | channel = wfcut.Replace((string)keysvals["channel"],"",99999); | ||
408 | |||
409 | if (keysvals.Contains("version")) | ||
410 | version = wfcut.Replace((string)keysvals["version"],"",99999); | ||
411 | |||
412 | if (keysvals.Contains("lang")) | ||
413 | lang = wfcut.Replace((string)keysvals["lang"],"",99999); | ||
414 | |||
415 | if (keysvals.Contains("password")) | ||
416 | password = wfcut.Replace((string)keysvals["password"], "", 99999); | ||
417 | |||
418 | |||
419 | // load our login form. | ||
420 | string loginform = GetLoginForm(firstname,lastname,location,region,grid,channel,version,lang,password,errormessages); | ||
367 | 421 | ||
368 | if (keysvals.ContainsKey("show_login_form")) | 422 | if (keysvals.ContainsKey("show_login_form")) |
369 | { | 423 | { |
370 | if ((string)keysvals["show_login_form"] == "TRUE") | 424 | if ((string)keysvals["show_login_form"] == "TRUE") |
371 | { | 425 | { |
372 | 426 | returnactions["int_response_code"] = statuscode; | |
427 | returnactions["str_response_string"] = loginform; | ||
373 | } | 428 | } |
374 | else | 429 | else |
375 | { | 430 | { |
431 | UserProfileData user = GetTheUser(firstname, lastname); | ||
432 | bool goodweblogin = false; | ||
376 | 433 | ||
434 | if (user != null) | ||
435 | goodweblogin = AuthenticateUser(user, password); | ||
436 | |||
437 | if (goodweblogin) | ||
438 | { | ||
439 | LLUUID webloginkey = LLUUID.Random(); | ||
440 | m_userManager.StoreWebLoginKey(user.UUID, webloginkey); | ||
441 | statuscode = 301; | ||
442 | |||
443 | string redirectURL = "secondlife:///app/login?first_name=" + firstname + "&last_name=" + | ||
444 | lastname + | ||
445 | "&location=" + location + "&grid=Other&web_login_key=" + webloginkey.ToString(); | ||
446 | |||
447 | returnactions["int_response_code"] = statuscode; | ||
448 | returnactions["str_redirect_location"] = redirectURL; | ||
449 | returnactions["str_response_string"] = "<HTML><BODY>GoodLogin</BODY></HTML>"; | ||
450 | } | ||
451 | else | ||
452 | { | ||
453 | errormessages = "The Username and password supplied did not match our records. Check your caps lock and try again"; | ||
454 | |||
455 | loginform = GetLoginForm(firstname, lastname, location, region, grid, channel, version, lang, password, errormessages); | ||
456 | returnactions["int_response_code"] = statuscode; | ||
457 | returnactions["str_response_string"] = loginform; | ||
458 | |||
459 | } | ||
377 | 460 | ||
378 | } | 461 | } |
379 | 462 | ||
@@ -382,16 +465,36 @@ namespace OpenSim.Framework.UserManagement | |||
382 | 465 | ||
383 | } | 466 | } |
384 | 467 | ||
385 | public string GetLoginForm() | 468 | public string GetLoginForm(string firstname, string lastname, string location, string region, |
469 | string grid, string channel, string version, string lang, | ||
470 | string password, string errormessages) | ||
386 | { | 471 | { |
472 | // inject our values in the form at the markers | ||
473 | |||
474 | string loginform=""; | ||
387 | string file = Path.Combine(Util.configDir(), "http_loginform.html"); | 475 | string file = Path.Combine(Util.configDir(), "http_loginform.html"); |
388 | if (!File.Exists(file)) | 476 | if (!File.Exists(file)) |
389 | return GetDefaultLoginForm(); | 477 | { |
390 | 478 | loginform = GetDefaultLoginForm(); | |
391 | StreamReader sr = File.OpenText(file); | 479 | } |
392 | string result = sr.ReadToEnd(); | 480 | else |
393 | sr.Close(); | 481 | { |
394 | return result; | 482 | StreamReader sr = File.OpenText(file); |
483 | loginform = sr.ReadToEnd(); | ||
484 | sr.Close(); | ||
485 | } | ||
486 | |||
487 | loginform = loginform.Replace("[$firstname]", firstname); | ||
488 | loginform = loginform.Replace("[$lastname]", lastname); | ||
489 | loginform = loginform.Replace("[$location]", location); | ||
490 | loginform = loginform.Replace("[$region]", region); | ||
491 | loginform = loginform.Replace("[$grid]", grid); | ||
492 | loginform = loginform.Replace("[$channel]", channel); | ||
493 | loginform = loginform.Replace("[$version]", version); | ||
494 | loginform = loginform.Replace("[$lang]", lang); | ||
495 | loginform = loginform.Replace("[$password]", password); | ||
496 | loginform = loginform.Replace("[$errors]", errormessages); | ||
497 | return loginform; | ||
395 | } | 498 | } |
396 | 499 | ||
397 | public string GetDefaultLoginForm() | 500 | public string GetDefaultLoginForm() |
@@ -405,7 +508,7 @@ namespace OpenSim.Framework.UserManagement | |||
405 | responseString = responseString + "<meta http-equiv=\"cache-control\" content=\"no-cache\">"; | 508 | responseString = responseString + "<meta http-equiv=\"cache-control\" content=\"no-cache\">"; |
406 | responseString = responseString + "<meta http-equiv=\"Pragma\" content=\"no-cache\">"; | 509 | responseString = responseString + "<meta http-equiv=\"Pragma\" content=\"no-cache\">"; |
407 | responseString = responseString + "<title>Second Life Login</title>"; | 510 | responseString = responseString + "<title>Second Life Login</title>"; |
408 | responseString = responseString + "<body>"; | 511 | responseString = responseString + "<body><br />"; |
409 | responseString = responseString + "<div id=\"login_box\">"; | 512 | responseString = responseString + "<div id=\"login_box\">"; |
410 | 513 | ||
411 | responseString = responseString + "<form action=\"/\" method=\"GET\" id=\"login-form\">"; | 514 | responseString = responseString + "<form action=\"/\" method=\"GET\" id=\"login-form\">"; |
@@ -434,6 +537,11 @@ namespace OpenSim.Framework.UserManagement | |||
434 | responseString = responseString + "<input type=\"hidden\" name=\"show_login_form\" value=\"FALSE\" />"; | 537 | responseString = responseString + "<input type=\"hidden\" name=\"show_login_form\" value=\"FALSE\" />"; |
435 | responseString = responseString + "<input type=\"hidden\" name=\"method\" value=\"login\" />"; | 538 | responseString = responseString + "<input type=\"hidden\" name=\"method\" value=\"login\" />"; |
436 | responseString = responseString + "<input type=\"hidden\" id=\"grid\" name=\"grid\" value=\"[$grid]\" />"; | 539 | responseString = responseString + "<input type=\"hidden\" id=\"grid\" name=\"grid\" value=\"[$grid]\" />"; |
540 | responseString = responseString + "<input type=\"hidden\" id=\"region\" name=\"region\" value=\"[$region]\" />"; | ||
541 | responseString = responseString + "<input type=\"hidden\" id=\"location\" name=\"location\" value=\"[$location]\" />"; | ||
542 | responseString = responseString + "<input type=\"hidden\" id=\"channel\" name=\"channel\" value=\"[$channel]\" />"; | ||
543 | responseString = responseString + "<input type=\"hidden\" id=\"version\" name=\"version\" value=\"[$version]\" />"; | ||
544 | responseString = responseString + "<input type=\"hidden\" id=\"lang\" name=\"lang\" value=\"[$lang]\" />"; | ||
437 | responseString = responseString + "<div id=\"submitbtn\">"; | 545 | responseString = responseString + "<div id=\"submitbtn\">"; |
438 | responseString = responseString + "<input class=\"input_over\" type=\"submit\" value=\"Connect\" />"; | 546 | responseString = responseString + "<input class=\"input_over\" type=\"submit\" value=\"Connect\" />"; |
439 | responseString = responseString + "</div>"; | 547 | responseString = responseString + "</div>"; |
@@ -444,7 +552,7 @@ namespace OpenSim.Framework.UserManagement | |||
444 | responseString = responseString + "<a href=\"http://www.secondlife.com/account/request.php\" target=\"_blank\">Forgot password?</a>"; | 552 | responseString = responseString + "<a href=\"http://www.secondlife.com/account/request.php\" target=\"_blank\">Forgot password?</a>"; |
445 | responseString = responseString + "</div>"; | 553 | responseString = responseString + "</div>"; |
446 | 554 | ||
447 | responseString = responseString + "<div id=\"channelinfo\"> [$clientchannelinfo] | [$clientversion]=[$clientlanguage]</div>"; | 555 | responseString = responseString + "<div id=\"channelinfo\"> [$channel] | [$version]=[$lang]</div>"; |
448 | responseString = responseString + "</form>"; | 556 | responseString = responseString + "</form>"; |
449 | responseString = responseString + "<script language=\"JavaScript\">"; | 557 | responseString = responseString + "<script language=\"JavaScript\">"; |
450 | responseString = responseString + "document.getElementById('firstname_input').focus();"; | 558 | responseString = responseString + "document.getElementById('firstname_input').focus();"; |
@@ -480,14 +588,26 @@ namespace OpenSim.Framework.UserManagement | |||
480 | "LOGIN", "Authenticating {0} {1} ({2})", profile.username, profile.surname, profile.UUID); | 588 | "LOGIN", "Authenticating {0} {1} ({2})", profile.username, profile.surname, profile.UUID); |
481 | 589 | ||
482 | // Web Login method seems to also occasionally send the hashed password itself | 590 | // Web Login method seems to also occasionally send the hashed password itself |
483 | |||
484 | 591 | ||
592 | |||
593 | // we do this to get our hash in a form that the server password code can consume | ||
594 | // when the web-login-form submits the password in the clear (supposed to be over SSL!) | ||
595 | if (!password.StartsWith("$1$")) | ||
596 | password = "$1$" + Util.Md5Hash(password); | ||
597 | |||
598 | |||
599 | |||
485 | password = password.Remove(0, 3); //remove $1$ | 600 | password = password.Remove(0, 3); //remove $1$ |
601 | |||
602 | |||
486 | 603 | ||
487 | string s = Util.Md5Hash(password + ":" + profile.passwordSalt); | 604 | string s = Util.Md5Hash(password + ":" + profile.passwordSalt); |
605 | // Testing... | ||
606 | //MainLog.Instance.Verbose("LOGIN", "SubHash:" + s + " userprofile:" + profile.passwordHash); | ||
607 | //MainLog.Instance.Verbose("LOGIN", "userprofile:" + profile.passwordHash + " SubCT:" + password); | ||
488 | 608 | ||
489 | passwordSuccess = (profile.passwordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) | 609 | passwordSuccess = (profile.passwordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) |
490 | || profile.passwordHash.Equals(password.ToString(),StringComparison.InvariantCultureIgnoreCase)); | 610 | || profile.passwordHash.Equals(password,StringComparison.InvariantCultureIgnoreCase)); |
491 | 611 | ||
492 | return passwordSuccess; | 612 | return passwordSuccess; |
493 | } | 613 | } |
diff --git a/OpenSim/Framework/Servers/BaseHttpServer.cs b/OpenSim/Framework/Servers/BaseHttpServer.cs index 32d65af..3ca4187 100644 --- a/OpenSim/Framework/Servers/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/BaseHttpServer.cs | |||
@@ -389,12 +389,9 @@ namespace OpenSim.Framework.Servers | |||
389 | foreach (string queryname in querystringkeys) | 389 | foreach (string queryname in querystringkeys) |
390 | { | 390 | { |
391 | keysvals.Add(queryname, request.QueryString[queryname]); | 391 | keysvals.Add(queryname, request.QueryString[queryname]); |
392 | MainLog.Instance.Warn("HTTP", queryname + "=" + request.QueryString[queryname]); | 392 | |
393 | } | 393 | } |
394 | //foreach (string headername in rHeaders) | 394 | |
395 | //{ | ||
396 | //MainLog.Instance.Warn("HEADER", headername + "=" + request.Headers[headername]); | ||
397 | //} | ||
398 | if (keysvals.Contains("method")) | 395 | if (keysvals.Contains("method")) |
399 | { | 396 | { |
400 | MainLog.Instance.Warn("HTTP", "Contains Method"); | 397 | MainLog.Instance.Warn("HTTP", "Contains Method"); |
diff --git a/OpenSim/Grid/UserServer/Main.cs b/OpenSim/Grid/UserServer/Main.cs index 02ca385..f40c6f6 100644 --- a/OpenSim/Grid/UserServer/Main.cs +++ b/OpenSim/Grid/UserServer/Main.cs | |||
@@ -103,6 +103,8 @@ namespace OpenSim.Grid.UserServer | |||
103 | BaseHttpServer httpServer = new BaseHttpServer(Cfg.HttpPort); | 103 | BaseHttpServer httpServer = new BaseHttpServer(Cfg.HttpPort); |
104 | 104 | ||
105 | httpServer.AddXmlRPCHandler("login_to_simulator", m_loginService.XmlRpcLoginMethod); | 105 | httpServer.AddXmlRPCHandler("login_to_simulator", m_loginService.XmlRpcLoginMethod); |
106 | |||
107 | httpServer.AddHTTPHandler("login", m_loginService.ProcessHTMLLogin); | ||
106 | 108 | ||
107 | httpServer.SetLLSDHandler(m_loginService.LLSDLoginMethod); | 109 | httpServer.SetLLSDHandler(m_loginService.LLSDLoginMethod); |
108 | 110 | ||
diff --git a/OpenSim/Region/Application/OpenSimMain.cs b/OpenSim/Region/Application/OpenSimMain.cs index 3818a50..297c9b2 100644 --- a/OpenSim/Region/Application/OpenSimMain.cs +++ b/OpenSim/Region/Application/OpenSimMain.cs | |||
@@ -323,7 +323,13 @@ namespace OpenSim | |||
323 | m_standaloneAuthenticate); | 323 | m_standaloneAuthenticate); |
324 | m_loginService.OnLoginToRegion += backendService.AddNewSession; | 324 | m_loginService.OnLoginToRegion += backendService.AddNewSession; |
325 | 325 | ||
326 | // XMLRPC action | ||
326 | m_httpServer.AddXmlRPCHandler("login_to_simulator", m_loginService.XmlRpcLoginMethod); | 327 | m_httpServer.AddXmlRPCHandler("login_to_simulator", m_loginService.XmlRpcLoginMethod); |
328 | |||
329 | // provides the web form login | ||
330 | m_httpServer.AddHTTPHandler("login", m_loginService.ProcessHTMLLogin); | ||
331 | |||
332 | // Provides the LLSD login | ||
327 | m_httpServer.SetLLSDHandler(m_loginService.LLSDLoginMethod); | 333 | m_httpServer.SetLLSDHandler(m_loginService.LLSDLoginMethod); |
328 | 334 | ||
329 | if (m_standaloneAuthenticate) | 335 | if (m_standaloneAuthenticate) |
diff --git a/OpenSim/Region/Communications/Local/LocalLoginService.cs b/OpenSim/Region/Communications/Local/LocalLoginService.cs index 2c92491..38f1970 100644 --- a/OpenSim/Region/Communications/Local/LocalLoginService.cs +++ b/OpenSim/Region/Communications/Local/LocalLoginService.cs | |||
@@ -103,12 +103,17 @@ namespace OpenSim.Region.Communications.Local | |||
103 | { | 103 | { |
104 | MainLog.Instance.Notice( | 104 | MainLog.Instance.Notice( |
105 | "LOGIN", "Authenticating " + profile.username + " " + profile.surname); | 105 | "LOGIN", "Authenticating " + profile.username + " " + profile.surname); |
106 | |||
107 | if (!password.StartsWith("$1$")) | ||
108 | password = "$1$" + Util.Md5Hash(password); | ||
106 | 109 | ||
107 | password = password.Remove(0, 3); //remove $1$ | 110 | password = password.Remove(0, 3); //remove $1$ |
108 | 111 | ||
109 | string s = Util.Md5Hash(password + ":" + profile.passwordSalt); | 112 | string s = Util.Md5Hash(password + ":" + profile.passwordSalt); |
110 | 113 | ||
111 | return profile.passwordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase); | 114 | bool loginresult = (profile.passwordHash.Equals(s.ToString(), StringComparison.InvariantCultureIgnoreCase) |
115 | || profile.passwordHash.Equals(password, StringComparison.InvariantCultureIgnoreCase)); | ||
116 | return loginresult; | ||
112 | } | 117 | } |
113 | } | 118 | } |
114 | 119 | ||
diff --git a/bin/http_loginform.html.example b/bin/http_loginform.html.example new file mode 100644 index 0000000..16655de --- /dev/null +++ b/bin/http_loginform.html.example | |||
@@ -0,0 +1,60 @@ | |||
1 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||
2 | <html xmlns="http://www.w3.org/1999/xhtml"> | ||
3 | <head> | ||
4 | <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> | ||
5 | <meta http-equiv="cache-control" content="no-cache"> | ||
6 | <meta http-equiv="Pragma" content="no-cache"> | ||
7 | <title>Second Life Login</title> | ||
8 | <body><br /> | ||
9 | <div id="login_box"> | ||
10 | |||
11 | <form action="/" method="GET" id="login-form"> | ||
12 | |||
13 | <div id="message">[$errors]</div> | ||
14 | <fieldset id="firstname"> | ||
15 | <legend>First Name:</legend> | ||
16 | <input type="text" id="firstname_input" size="15" maxlength="100" name="username" value="[$firstname]" /> | ||
17 | </fieldset> | ||
18 | <fieldset id="lastname"> | ||
19 | <legend>Last Name:</legend> | ||
20 | <input type="text" size="15" maxlength="100" name="lastname" value="[$lastname]" /> | ||
21 | </fieldset> | ||
22 | <fieldset id="password"> | ||
23 | <legend>Password:</legend> | ||
24 | <table cellspacing="0" cellpadding="0" border="0"> | ||
25 | <tr> | ||
26 | <td colspan="2"><input type="password" size="15" maxlength="100" name="password" value="[$password]" /></td> | ||
27 | </tr> | ||
28 | <tr> | ||
29 | <td valign="middle"><input type="checkbox" name="remember_password" id="remember_password" [$remember_password] style="margin-left:0px;"/></td> | ||
30 | <td><label for="remember_password">Remember password</label></td>"; | ||
31 | </tr> | ||
32 | </table> | ||
33 | </fieldset> | ||
34 | <input type="hidden" name="show_login_form" value="FALSE" /> | ||
35 | <input type="hidden" name="method" value="login" /> | ||
36 | <input type="hidden" id="grid" name="grid" value="[$grid]" /> | ||
37 | <input type="hidden" id="region" name="region" value="[$region]" /> | ||
38 | <input type="hidden" id="location" name="location" value="[$location]" /> | ||
39 | <input type="hidden" id="channel" name="channel" value="[$channel]" /> | ||
40 | <input type="hidden" id="version" name="version" value="[$version]" /> | ||
41 | <input type="hidden" id="lang" name="lang" value="[$lang]" /> | ||
42 | <div id="submitbtn"> | ||
43 | <input class="input_over" type="submit" value="Connect" /> | ||
44 | </div> | ||
45 | <div id="connecting" style="visibility:hidden"> Connecting...</div> | ||
46 | |||
47 | <div id="helplinks"> | ||
48 | <a href="http://www.secondlife.com/join/index.php" target="_blank">Create new account</a> | | ||
49 | <a href="http://www.secondlife.com/account/request.php" target="_blank">Forgot password?</a> | ||
50 | </div> | ||
51 | |||
52 | <div id="channelinfo"> [$channel] | [$version]=[$lang]</div> | ||
53 | </form> | ||
54 | <script language="JavaScript"> | ||
55 | document.getElementById('firstname_input').focus(); | ||
56 | </script> | ||
57 | </div> | ||
58 | </div> | ||
59 | </body> | ||
60 | </html> \ No newline at end of file | ||