diff --git a/l2j-server-login/.mvn/wrapper/maven-wrapper.jar b/l2j-server-login/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..eebc3c18 Binary files /dev/null and b/l2j-server-login/.mvn/wrapper/maven-wrapper.jar differ diff --git a/l2j-server-login/.mvn/wrapper/maven-wrapper.properties b/l2j-server-login/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..e364bfe9 --- /dev/null +++ b/l2j-server-login/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,4 @@ +wrapperVersion=3.3.4 +distributionType=script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar diff --git a/l2j-server-login/LICENSE.md b/l2j-server-login/LICENSE.md new file mode 100644 index 00000000..61b142f0 --- /dev/null +++ b/l2j-server-login/LICENSE.md @@ -0,0 +1,636 @@ +# GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 [Free Software Foundation, Inc.](http://fsf.org/) + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and +other kinds of works. + +The licenses for most software and other practical works are designed to take +away your freedom to share and change the works. By contrast, the GNU General +Public License is intended to guarantee your freedom to share and change all +versions of a program--to make sure it remains free software for all its users. +We, the Free Software Foundation, use the GNU General Public License for most +of our software; it applies also to any other work released this way by its +authors. You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the freedom to +distribute copies of free software (and charge for them if you wish), that you +receive source code or can get it if you want it, that you can change the +software or use pieces of it in new free programs, and that you know you can do +these things. + +To protect your rights, we need to prevent others from denying you these rights +or asking you to surrender the rights. Therefore, you have certain +responsibilities if you distribute copies of the software, or if you modify it: +responsibilities to respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must pass on to the recipients the same freedoms that you received. +You must make sure that they, too, receive or can get the source code. And you +must show them these terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: + + 1. assert copyright on the software, and + 2. offer you this License giving you legal permission to copy, distribute + and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that +there is no warranty for this free software. For both users' and authors' sake, +the GPL requires that modified versions be marked as changed, so that their +problems will not be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified +versions of the software inside them, although the manufacturer can do so. This +is fundamentally incompatible with the aim of protecting users' freedom to +change the software. The systematic pattern of such abuse occurs in the area of +products for individuals to use, which is precisely where it is most +unacceptable. Therefore, we have designed this version of the GPL to prohibit +the practice for those products. If such problems arise substantially in other +domains, we stand ready to extend this provision to those domains in future +versions of the GPL, as needed to protect the freedom of users. + +Finally, every program is threatened constantly by software patents. States +should not allow patents to restrict development and use of software on +general-purpose computers, but in those that do, we wish to avoid the special +danger that patents applied to a free program could make it effectively +proprietary. To prevent this, the GPL assures that patents cannot be used to +render the program non-free. + +The precise terms and conditions for copying, distribution and modification +follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +*This License* refers to version 3 of the GNU General Public License. + +*Copyright* also means copyright-like laws that apply to other kinds of works, +such as semiconductor masks. + +*The Program* refers to any copyrightable work licensed under this License. +Each licensee is addressed as *you*. *Licensees* and *recipients* may be +individuals or organizations. + +To *modify* a work means to copy from or adapt all or part of the work in a +fashion requiring copyright permission, other than the making of an exact copy. +The resulting work is called a *modified version* of the earlier work or a work +*based on* the earlier work. + +A *covered work* means either the unmodified Program or a work based on the +Program. + +To *propagate* a work means to do anything with it that, without permission, +would make you directly or secondarily liable for infringement under applicable +copyright law, except executing it on a computer or modifying a private copy. +Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To *convey* a work means any kind of propagation that enables other parties to +make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays *Appropriate Legal Notices* to the +extent that it includes a convenient and prominently visible feature that + + 1. displays an appropriate copyright notice, and + 2. tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the work + under this License, and how to view a copy of this License. + +If the interface presents a list of user commands or options, such as a menu, a +prominent item in the list meets this criterion. + +### 1. Source Code. + +The *source code* for a work means the preferred form of the work for making +modifications to it. *Object code* means any non-source form of a work. + +A *Standard Interface* means an interface that either is an official standard +defined by a recognized standards body, or, in the case of interfaces specified +for a particular programming language, one that is widely used among developers +working in that language. + +The *System Libraries* of an executable work include anything, other than the +work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only +to enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code +form. A *Major Component*, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on +which the executable work runs, or a compiler used to produce the work, or an +object code interpreter used to run it. + +The *Corresponding Source* for a work in object code form means all the source +code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. +However, it does not include the work's System Libraries, or general-purpose +tools or generally available free programs which are used unmodified in +performing those activities but which are not part of the work. For example, +Corresponding Source includes interface definition files associated with source +files for the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, such as +by intimate data communication or control flow between those subprograms and +other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on +the Program, and are irrevocable provided the stated conditions are met. This +License explicitly affirms your unlimited permission to run the unmodified +Program. The output from running a covered work is covered by this License only +if the output, given its content, constitutes a covered work. This License +acknowledges your rights of fair use or other equivalent, as provided by +copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey +covered works to others for the sole purpose of having them make modifications +exclusively for you, or provide you with facilities for running those works, +provided that you comply with the terms of this License in conveying all +material for which you do not control copyright. Those thus making or running +the covered works for you must do so exclusively on your behalf, under your +direction and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + +Conveying under any other circumstances is permitted solely under the +conditions stated below. Sublicensing is not allowed; section 10 makes it +unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure +under any applicable law fulfilling obligations under article 11 of the WIPO +copyright treaty adopted on 20 December 1996, or similar laws prohibiting or +restricting circumvention of such measures. + +When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention is +effected by exercising rights under this License with respect to the covered +work, and you disclaim any intention to limit operation or modification of the +work as a means of enforcing, against the work's users, your or third parties' +legal rights to forbid circumvention of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, +in any medium, provided that you conspicuously and appropriately publish on +each copy an appropriate copyright notice; keep intact all notices stating that +this License and any non-permissive terms added in accord with section 7 apply +to the code; keep intact all notices of the absence of any warranty; and give +all recipients a copy of this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may +offer support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it +from the Program, in the form of source code under the terms of section 4, +provided that you also meet all of these conditions: + + - a) The work must carry prominent notices stating that you modified it, and + giving a relevant date. + - b) The work must carry prominent notices stating that it is released under + this License and any conditions added under section 7. This requirement + modifies the requirement in section 4 to *keep intact all notices*. + - c) You must license the entire work, as a whole, under this License to + anyone who comes into possession of a copy. This License will therefore + apply, along with any applicable section 7 additional terms, to the whole + of the work, and all its parts, regardless of how they are packaged. This + License gives no permission to license the work in any other way, but it + does not invalidate such permission if you have separately received it. + - d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your work need + not make them do so. + +A compilation of a covered work with other separate and independent works, +which are not by their nature extensions of the covered work, and which are not +combined with it such as to form a larger program, in or on a volume of a +storage or distribution medium, is called an *aggregate* if the compilation and +its resulting copyright are not used to limit the access or legal rights of the +compilation's users beyond what the individual works permit. Inclusion of a +covered work in an aggregate does not cause this License to apply to the other +parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 +and 5, provided that you also convey the machine-readable Corresponding Source +under the terms of this License, in one of these ways: + + - a) Convey the object code in, or embodied in, a physical product (including + a physical distribution medium), accompanied by the Corresponding Source + fixed on a durable physical medium customarily used for software + interchange. + - b) Convey the object code in, or embodied in, a physical product (including + a physical distribution medium), accompanied by a written offer, valid for + at least three years and valid for as long as you offer spare parts or + customer support for that product model, to give anyone who possesses the + object code either + 1. a copy of the Corresponding Source for all the software in the product + that is covered by this License, on a durable physical medium + customarily used for software interchange, for a price no more than your + reasonable cost of physically performing this conveying of source, or + 2. access to copy the Corresponding Source from a network server at no + charge. + - c) Convey individual copies of the object code with a copy of the written + offer to provide the Corresponding Source. This alternative is allowed only + occasionally and noncommercially, and only if you received the object code + with such an offer, in accord with subsection 6b. + - d) Convey the object code by offering access from a designated place + (gratis or for a charge), and offer equivalent access to the Corresponding + Source in the same way through the same place at no further charge. You + need not require recipients to copy the Corresponding Source along with the + object code. If the place to copy the object code is a network server, the + Corresponding Source may be on a different server operated by you or a + third party) that supports equivalent copying facilities, provided you + maintain clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the Corresponding + Source, you remain obligated to ensure that it is available for as long as + needed to satisfy these requirements. + - e) Convey the object code using peer-to-peer transmission, provided you + inform other peers where the object code and Corresponding Source of the + work are being offered to the general public at no charge under subsection + 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A *User Product* is either + + 1. a *consumer product*, which means any tangible personal property which is + normally used for personal, family, or household purposes, or + 2. anything designed or sold for incorporation into a dwelling. + +In determining whether a product is a consumer product, doubtful cases shall be +resolved in favor of coverage. For a particular product received by a +particular user, *normally used* refers to a typical or common use of that +class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, +the product. A product is a consumer product regardless of whether the product +has substantial commercial, industrial or non-consumer uses, unless such uses +represent the only significant mode of use of the product. + +*Installation Information* for a User Product means any methods, procedures, +authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified +version of its Corresponding Source. The information must suffice to ensure +that the continued functioning of the modified object code is in no case +prevented or interfered with solely because modification has been made. + +If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as part of a +transaction in which the right of possession and use of the User Product is +transferred to the recipient in perpetuity or for a fixed term (regardless of +how the transaction is characterized), the Corresponding Source conveyed under +this section must be accompanied by the Installation Information. But this +requirement does not apply if neither you nor any third party retains the +ability to install modified object code on the User Product (for example, the +work has been installed in ROM). + +The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates for a +work that has been modified or installed by the recipient, or for the User +Product in which it has been modified or installed. Access to a network may be +denied when the modification itself materially and adversely affects the +operation of the network or violates the rules and protocols for communication +across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord +with this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require +no special password or key for unpacking, reading or copying. + +### 7. Additional Terms. + +*Additional permissions* are terms that supplement the terms of this License by +making exceptions from one or more of its conditions. Additional permissions +that are applicable to the entire Program shall be treated as though they were +included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may +be used separately under those permissions, but the entire Program remains +governed by this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when +you modify the work.) You may place additional permissions on material, added +by you to a covered work, for which you have or can give appropriate copyright +permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + + - a) Disclaiming warranty or limiting liability differently from the terms of + sections 15 and 16 of this License; or + - b) Requiring preservation of specified reasonable legal notices or author + attributions in that material or in the Appropriate Legal Notices displayed + by works containing it; or + - c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in reasonable + ways as different from the original version; or + - d) Limiting the use for publicity purposes of names of licensors or authors + of the material; or + - e) Declining to grant rights under trademark law for use of some trade + names, trademarks, or service marks; or + - f) Requiring indemnification of licensors and authors of that material by + anyone who conveys the material (or modified versions of it) with + contractual assumptions of liability to the recipient, for any liability + that these contractual assumptions directly impose on those licensors and + authors. + +All other non-permissive additional terms are considered *further restrictions* +within the meaning of section 10. If the Program as you received it, or any +part of it, contains a notice stating that it is governed by this License along +with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or +conveying under this License, you may add to a covered work material governed +by the terms of that license document, provided that the further restriction +does not survive such relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, +in the relevant source files, a statement of the additional terms that apply to +those files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements +apply either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly provided +under this License. Any attempt otherwise to propagate or modify it is void, +and will automatically terminate your rights under this License (including any +patent licenses granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated + + - a) provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and + - b) permanently, if the copyright holder fails to notify you of the + violation by some reasonable means prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated +permanently if the copyright holder notifies you of the violation by some +reasonable means, this is the first time you have received notice of violation +of this License (for any work) from that copyright holder, and you cure the +violation prior to 30 days after your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses +of parties who have received copies or rights from you under this License. If +your rights have been terminated and not permanently reinstated, you do not +qualify to receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy +of the Program. Ancillary propagation of a covered work occurring solely as a +consequence of using peer-to-peer transmission to receive a copy likewise does +not require acceptance. However, nothing other than this License grants you +permission to propagate or modify any covered work. These actions infringe +copyright if you do not accept this License. Therefore, by modifying or +propagating a covered work, you indicate your acceptance of this License to do +so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a +license from the original licensors, to run, modify and propagate that work, +subject to this License. You are not responsible for enforcing compliance by +third parties with this License. + +An *entity transaction* is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered work +results from an entity transaction, each party to that transaction who receives +a copy of the work also receives whatever licenses to the work the party's +predecessor in interest had or could give under the previous paragraph, plus a +right to possession of the Corresponding Source of the work from the +predecessor in interest, if the predecessor has it or can get it with +reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights +granted or affirmed under this License. For example, you may not impose a +license fee, royalty, or other charge for exercise of rights granted under this +License, and you may not initiate litigation (including a cross-claim or +counterclaim in a lawsuit) alleging that any patent claim is infringed by +making, using, selling, offering for sale, or importing the Program or any +portion of it. + +### 11. Patents. + +A *contributor* is a copyright holder who authorizes use under this License of +the Program or a work on which the Program is based. The work thus licensed is +called the contributor's *contributor version*. + +A contributor's *essential patent claims* are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, +that would be infringed by some manner, permitted by this License, of making, +using, or selling its contributor version, but do not include claims that would +be infringed only as a consequence of further modification of the contributor +version. For purposes of this definition, *control* includes the right to grant +patent sublicenses in a manner consistent with the requirements of this +License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent +license under the contributor's essential patent claims, to make, use, sell, +offer for sale, import and otherwise run, modify and propagate the contents of +its contributor version. + +In the following three paragraphs, a *patent license* is any express agreement +or commitment, however denominated, not to enforce a patent (such as an express +permission to practice a patent or covenant not to sue for patent +infringement). To *grant* such a patent license to a party means to make such +an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of +charge and under the terms of this License, through a publicly available +network server or other readily accessible means, then you must either + + 1. cause the Corresponding Source to be so available, or + 2. arrange to deprive yourself of the benefit of the patent license for this + particular work, or + 3. arrange, in a manner consistent with the requirements of this License, to + extend the patent license to downstream recipients. + +*Knowingly relying* means you have actual knowledge that, but for the patent +license, your conveying the covered work in a country, or your recipient's use +of the covered work in a country, would infringe one or more identifiable +patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a +patent license to some of the parties receiving the covered work authorizing +them to use, propagate, modify or convey a specific copy of the covered work, +then the patent license you grant is automatically extended to all recipients +of the covered work and works based on it. + +A patent license is *discriminatory* if it does not include within the scope of +its coverage, prohibits the exercise of, or is conditioned on the non-exercise +of one or more of the rights that are specifically granted under this License. +You may not convey a covered work if you are a party to an arrangement with a +third party that is in the business of distributing software, under which you +make payment to the third party based on the extent of your activity of +conveying the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory patent +license + + - a) in connection with copies of the covered work conveyed by you (or copies + made from those copies), or + - b) primarily for and in connection with specific products or compilations + that contain the covered work, unless you entered into that arrangement, or + that patent license was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to +you under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not excuse +you from the conditions of this License. If you cannot convey a covered work so +as to satisfy simultaneously your obligations under this License and any other +pertinent obligations, then as a consequence you may not convey it at all. For +example, if you agree to terms that obligate you to collect a royalty for +further conveying from those to whom you convey the Program, the only way you +could satisfy both those terms and this License would be to refrain entirely +from conveying the Program. + +### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to +link or combine any covered work with a work licensed under version 3 of the +GNU Affero General Public License into a single combined work, and to convey +the resulting work. The terms of this License will continue to apply to the +part which is the covered work, but the special requirements of the GNU Affero +General Public License, section 13, concerning interaction through a network +will apply to the combination as such. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in +spirit to the present version, but may differ in detail to address new problems +or concerns. + +Each version is given a distinguishing version number. If the Program specifies +that a certain numbered version of the GNU General Public License *or any later +version* applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by +the Free Software Foundation. If the Program does not specify a version number +of the GNU General Public License, you may choose any version ever published by +the Free Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the +GNU General Public License can be used, that proxy's public statement of +acceptance of a version permanently authorizes you to choose that version for +the Program. + +Later license versions may give you additional or different permissions. +However, no additional obligations are imposed on any author or copyright +holder as a result of your choosing to follow a later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE +LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER +PARTIES PROVIDE THE PROGRAM *AS IS* WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR +CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE +THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED +INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY +HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot +be given local legal effect according to their terms, reviewing courts shall +apply local law that most closely approximates an absolute waiver of all civil +liability in connection with the Program, unless a warranty or assumption of +liability accompanies a copy of the Program in return for a fee. + +## END OF TERMS AND CONDITIONS ### + +### How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible +use to the public, the best way to achieve this is to make it free software +which everyone can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach +them to the start of each source file to most effectively state the exclusion +of warranty; and each file should have at least the *copyright* line and a +pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like +this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate +parts of the General Public License. Of course, your program's commands might +be different; for a GUI interface, you would use an *about box*. + +You should also get your employer (if you work as a programmer) or school, if +any, to sign a *copyright disclaimer* for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +[http://www.gnu.org/licenses/](http://www.gnu.org/licenses/). + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider +it more useful to permit linking proprietary applications with the library. If +this is what you want to do, use the GNU Lesser General Public License instead +of this License. But first, please read +[http://www.gnu.org/philosophy/why-not-lgpl.html](http://www.gnu.org/philosophy/why-not-lgpl.html). \ No newline at end of file diff --git a/l2j-server-login/README.md b/l2j-server-login/README.md new file mode 100644 index 00000000..71109590 --- /dev/null +++ b/l2j-server-login/README.md @@ -0,0 +1,20 @@ +L2J Server: Login +=== + +L2J Server: Login is the authentication server, users are redirected to a game server afterward. + +![](./src/main/resources/l2j-server-login-logo_256x256.png) + + +Links +--- + +- [Web Site](http://www.l2jserver.com) + +- [Forums](http://www.l2jserver.com/forum/) + +- [Discord](https://discord.gg/AzHh7e2Sej) + +- [Trello](https://trello.com/b/qjLoH966) + +- [@l2jserver](https://twitter.com/l2jserver) \ No newline at end of file diff --git a/l2j-server-login/bitbucket-pipelines.yml b/l2j-server-login/bitbucket-pipelines.yml new file mode 100644 index 00000000..7a726436 --- /dev/null +++ b/l2j-server-login/bitbucket-pipelines.yml @@ -0,0 +1,22 @@ +image: eclipse-temurin:25-jdk-ubi10-minimal + +pipelines: + default: + - step: + caches: + - maven + script: + - /bin/sh mvnw install + artifacts: + - target/*.zip + - step: + deployment: Production + script: + - pipe: atlassian/ftp-deploy:0.7.1 + variables: + USER: $FTP_USER + PASSWORD: $FTP_PASSWORD + SERVER: $FTP_HOST + REMOTE_PATH: $FTP_REMOTE_PATH + LOCAL_PATH: target/ + SET_ARGS: "ftp:ssl-allow yes ftp:ssl-force yes ftp:ssl-protect-data yes ssl:verify-certificate false" \ No newline at end of file diff --git a/l2j-server-login/mvnw b/l2j-server-login/mvnw new file mode 100644 index 00000000..1ddd97b9 --- /dev/null +++ b/l2j-server-login/mvnw @@ -0,0 +1,338 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ]; then + + if [ -f /usr/local/etc/mavenrc ]; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ]; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ]; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false +darwin=false +mingw=false +case "$(uname)" in +CYGWIN*) cygwin=true ;; +MINGW*) mingw=true ;; +Darwin*) + darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)" + export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home" + export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ]; then + if [ -r /etc/gentoo-release ]; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \ + && JAVA_HOME="$( + cd "$JAVA_HOME" || ( + echo "cannot cd into $JAVA_HOME." >&2 + exit 1 + ) + pwd + )" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin; then + javaHome="$(dirname "$javaExecutable")" + javaExecutable="$(cd "$javaHome" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "$javaExecutable")" + fi + javaHome="$(dirname "$javaExecutable")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ]; then + if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$( + \unset -f command 2>/dev/null + \command -v java + )" + fi +fi + +if [ ! -x "$JAVACMD" ]; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ]; then + echo "Warning: JAVA_HOME environment variable is not set." >&2 +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ]; then + echo "Path not specified to find_maven_basedir" >&2 + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ]; do + if [ -d "$wdir"/.mvn ]; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$( + cd "$wdir/.." || exit 1 + pwd + ) + fi + # end of workaround + done + printf '%s' "$( + cd "$basedir" || exit 1 + pwd + )" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' <"$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1 +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + fi + while IFS="=" read -r key value; do + case "$key" in wrapperUrl) + wrapperUrl=$(trim "${value-}") + break + ;; + esac + done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget >/dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget ${QUIET:+"$QUIET"} "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget ${QUIET:+"$QUIET"} --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl >/dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl ${QUIET:+"$QUIET"} -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl ${QUIET:+"$QUIET"} --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in wrapperSha256Sum) + wrapperSha256Sum=$(trim "${value-}") + break + ;; + esac +done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c - >/dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] \ + && JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] \ + && CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] \ + && MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/l2j-server-login/mvnw.cmd b/l2j-server-login/mvnw.cmd new file mode 100644 index 00000000..c453424c --- /dev/null +++ b/l2j-server-login/mvnw.cmd @@ -0,0 +1,206 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. >&2 +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. >&2 +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. >&2 +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.4/maven-wrapper-3.3.4.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash;"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Error 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Error 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Error 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/l2j-server-login/pom.xml b/l2j-server-login/pom.xml new file mode 100644 index 00000000..53bbdc6a --- /dev/null +++ b/l2j-server-login/pom.xml @@ -0,0 +1,144 @@ + + 4.0.0 + com.l2jserver + l2j-server-login + 2.6.8.0 + L2J Login Server + L2J Login Server handles the game client authentication and login. + https://bitbucket.org/l2jserver/l2j-server-login/ + + UTF-8 + UTF-8 + 25 + 25 + + 2.25.3 + 2.1.5 + 1.0.12 + + 2.6.9.0 + + 8 + 12.2.0 + 3.8.0 + 3.15.0 + 3.5.0 + + + + GPL 3.0 + https://www.gnu.org/licenses/gpl-3.0.html + + + + L2J Server + https://l2jserver.com + + + + jitpack.io + https://jitpack.io + + + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j.version} + + + org.apache.logging.log4j + log4j-core + ${log4j.version} + + + org.aeonbits.owner + owner-java8 + ${owner.version} + + + jakarta.mail + jakarta.mail-api + ${jakarta.mail-api.version} + + + + org.bitbucket.l2jserver + l2j-server-commons + ${l2j-server-commons.version} + + + + l2jlogin + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + full + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + true + libs/ + com.l2jserver.loginserver.LoginServer + + + + + + org.apache.maven.plugins + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + package + + single + + + ${project.artifactId}-${project.version} + false + + src/main/assembly/zip.xml + + + + + + + + + + ci + + + + org.owasp + dependency-check-maven + ${dependency-check-maven.version} + + ${dependency-check-maven.level} + false + ${env.NVD_API_KEY} + + + + + check + + + + + + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/assembly/zip.xml b/l2j-server-login/src/main/assembly/zip.xml new file mode 100644 index 00000000..9b8aa146 --- /dev/null +++ b/l2j-server-login/src/main/assembly/zip.xml @@ -0,0 +1,47 @@ + + zip + false + + zip + + + + ${project.build.directory}/l2jlogin.jar + . + + + src/main/resources/LoginServer_loop.sh + . + + + src/main/resources/startLoginServer.bat + . + + + src/main/resources/startLoginServer.sh + . + + + + + src/main/resources/ + + config/** + data/** + sql/** + + . + + + + + libs + + ${project.groupId}:${project.artifactId}:jar:* + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/FloodProtectedListener.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/FloodProtectedListener.java new file mode 100644 index 00000000..f43daffa --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/FloodProtectedListener.java @@ -0,0 +1,133 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Flood Protected listener. + * @author -Wooden- + * @version 2.6.1.0 + */ +public abstract class FloodProtectedListener extends Thread { + + private static final Logger LOG = LoggerFactory.getLogger(FloodProtectedListener.class); + + private final Map _floodProtection = new ConcurrentHashMap<>(); + + private final ServerSocket _serverSocket; + + public FloodProtectedListener(String listenIp, int port) throws Exception { + if (listenIp.equals("*")) { + _serverSocket = new ServerSocket(port); + } else { + _serverSocket = new ServerSocket(port, 50, InetAddress.getByName(listenIp)); + } + } + + @Override + public void run() { + Socket connection; + while (!isInterrupted()) { + try { + connection = _serverSocket.accept(); + if (server().isFloodProtectionEnabled()) { + ForeignConnection fConnection = _floodProtection.get(connection.getInetAddress().getHostAddress()); + if (fConnection != null) { + fConnection.connectionNumber += 1; + if (((fConnection.connectionNumber > server().getFastConnectionLimit()) && // + ((System.currentTimeMillis() - fConnection.lastConnection) < server().getNormalConnectionTime())) || // + ((System.currentTimeMillis() - fConnection.lastConnection) < server().getFastConnectionTime()) || // + (fConnection.connectionNumber > server().getMaxConnectionPerIP())) { + fConnection.lastConnection = System.currentTimeMillis(); + connection.close(); + fConnection.connectionNumber -= 1; + if (!fConnection.isFlooding) { + LOG.warn("Potential Flood from {}!", connection.getInetAddress().getHostAddress()); + } + fConnection.isFlooding = true; + continue; + } + if (fConnection.isFlooding) // if connection was flooding server but now passed the check + { + fConnection.isFlooding = false; + LOG.info("Connection {} is not considered as flooding anymore.", connection.getInetAddress().getHostAddress()); + } + fConnection.lastConnection = System.currentTimeMillis(); + } else { + fConnection = new ForeignConnection(System.currentTimeMillis()); + _floodProtection.put(connection.getInetAddress().getHostAddress(), fConnection); + } + } + + addClient(connection); + } catch (Exception e) { + if (isInterrupted()) { + close(); + break; + } + } + } + } + + protected static class ForeignConnection { + public int connectionNumber; + public long lastConnection; + public boolean isFlooding = false; + + public ForeignConnection(long time) { + lastConnection = time; + connectionNumber = 1; + } + } + + public abstract void addClient(Socket s); + + public void removeFloodProtection(String ip) { + if (!server().isFloodProtectionEnabled()) { + return; + } + ForeignConnection fConnection = _floodProtection.get(ip); + if (fConnection != null) { + fConnection.connectionNumber -= 1; + if (fConnection.connectionNumber == 0) { + _floodProtection.remove(ip); + } + } else { + LOG.warn("Removing a flood protection for a Game Server ({}) that was not in the connection map???", ip); + } + } + + public void close() { + try { + _serverSocket.close(); + } catch (Exception ex) { + LOG.warn("There has been an error closing the connection!", ex); + } + } +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerListener.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerListener.java new file mode 100644 index 00000000..707c7c53 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerListener.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import java.net.Socket; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Game Server listener. + * @author KenM + * @version 2.6.1.0 + */ +public class GameServerListener extends FloodProtectedListener { + private static final List _gameServers = new CopyOnWriteArrayList<>(); + + public GameServerListener() throws Exception { + super(server().getGameServerHost(), server().getGameServerPort()); + setName(getClass().getSimpleName()); + } + + @Override + public void addClient(Socket s) { + _gameServers.add(new GameServerThread(s)); + } + + public void removeGameServer(GameServerThread gst) { + _gameServers.remove(gst); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerTable.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerTable.java new file mode 100644 index 00000000..59a07ae0 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerTable.java @@ -0,0 +1,552 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.database.ConnectionFactory; +import com.l2jserver.commons.util.Rnd; +import com.l2jserver.loginserver.network.gameserverpackets.ServerStatus; +import com.l2jserver.loginserver.util.IPSubnet; + +/** + * Loads the game server names and initialize the game server tables. + * @author KenM + * @author Zoey76 + * @version 2.6.1.0 + */ +public final class GameServerTable { + + private static final Logger LOG = LoggerFactory.getLogger(GameServerTable.class); + + private static final Map GAME_SERVER_TABLE = new HashMap<>(); + + private static final int KEYS_SIZE = 10; + + private KeyPair[] _keyPairs; + + public GameServerTable() { + loadRegisteredGameServers(); + LOG.info("{}: Loaded {} registered Game Servers.", getClass().getSimpleName(), GAME_SERVER_TABLE.size()); + + initRSAKeys(); + LOG.info("{}: Cached {} RSA keys for Game Server communication.", getClass().getSimpleName(), _keyPairs.length); + } + + private void initRSAKeys() { + try { + final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); + keyGen.initialize(new RSAKeyGenParameterSpec(512, RSAKeyGenParameterSpec.F4)); + _keyPairs = new KeyPair[KEYS_SIZE]; + for (int i = 0; i < KEYS_SIZE; i++) { + _keyPairs[i] = keyGen.genKeyPair(); + } + } catch (Exception e) { + LOG.error("{}: Error loading RSA keys for Game Server communication!", getClass().getSimpleName(), e); + } + } + + private void loadRegisteredGameServers() { + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.createStatement(); + var rs = ps.executeQuery("SELECT * FROM gameservers")) { + int id; + while (rs.next()) { + id = rs.getInt("server_id"); + GAME_SERVER_TABLE.put(id, new GameServerInfo(id, stringToHex(rs.getString("hexid")))); + } + } catch (Exception e) { + LOG.error("{}: Error loading registered game servers!", getClass().getSimpleName(), e); + } + } + + /** + * Gets the registered game servers. + * @return the registered game servers + */ + public Map getRegisteredGameServers() { + return GAME_SERVER_TABLE; + } + + /** + * Gets the registered game server by id. + * @param id the game server ID + * @return the registered game server by id + */ + public GameServerInfo getRegisteredGameServerById(int id) { + return GAME_SERVER_TABLE.get(id); + } + + /** + * Checks for registered game server on id. + * @param id the id + * @return true, if successful + */ + public boolean hasRegisteredGameServerOnId(int id) { + return GAME_SERVER_TABLE.containsKey(id); + } + + /** + * Register with first available id. + * @param gsi the game server information DTO + * @return true, if successful + */ + public boolean registerWithFirstAvailableId(GameServerInfo gsi) { + // avoid two servers registering with the same "free" id + synchronized (GAME_SERVER_TABLE) { + for (Integer serverId : ServerNameDAO.getServers().keySet()) { + if (!GAME_SERVER_TABLE.containsKey(serverId)) { + GAME_SERVER_TABLE.put(serverId, gsi); + gsi.setId(serverId); + return true; + } + } + } + return false; + } + + /** + * Register a game server. + * @param id the id + * @param gsi the gsi + * @return true, if successful + */ + public boolean register(int id, GameServerInfo gsi) { + // avoid two servers registering with the same id + synchronized (GAME_SERVER_TABLE) { + if (!GAME_SERVER_TABLE.containsKey(id)) { + GAME_SERVER_TABLE.put(id, gsi); + return true; + } + } + return false; + } + + /** + * Wrapper method. + * @param gsi the game server info DTO. + */ + public void registerServerOnDB(GameServerInfo gsi) { + registerServerOnDB(gsi.getHexId(), gsi.getId(), gsi.getExternalHost()); + } + + /** + * Register server on db. + * @param hexId the hex id + * @param id the id + * @param externalHost the external host + */ + public void registerServerOnDB(byte[] hexId, int id, String externalHost) { + register(id, new GameServerInfo(id, hexId)); + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement("INSERT INTO gameservers (hexid,server_id,host) values (?,?,?)")) { + ps.setString(1, hexToString(hexId)); + ps.setInt(2, id); + ps.setString(3, externalHost); + ps.executeUpdate(); + } catch (Exception e) { + LOG.error("{}: Error while saving game server!", getClass().getSimpleName(), e); + } + } + + /** + * Gets the key pair. + * @return a random key pair. + */ + public KeyPair getKeyPair() { + return _keyPairs[Rnd.nextInt(10)]; + } + + /** + * String to hex. + * @param string the string to convert. + * @return return the hex representation. + */ + private byte[] stringToHex(String string) { + return new BigInteger(string, 16).toByteArray(); + } + + /** + * Hex to string. + * @param hex the hex value to convert. + * @return the string representation. + */ + private String hexToString(byte[] hex) { + if (hex == null) { + return "null"; + } + return new BigInteger(hex).toString(16); + } + + /** + * The Class GameServerInfo. + */ + public static class GameServerInfo { + // auth + private int _id; + private final byte[] _hexId; + private boolean _isAuthed; + // status + private GameServerThread _gst; + private int _status; + // network + private final ArrayList _addresses = new ArrayList<>(5); + private int _port; + // config + private final boolean _isPvp = true; + private int _serverType; + private int _ageLimit; + private boolean _isShowingBrackets; + private int _maxPlayers; + + /** + * Instantiates a new game server info. + * @param id the id + * @param hexId the hex id + * @param gst the gst + */ + public GameServerInfo(int id, byte[] hexId, GameServerThread gst) { + _id = id; + _hexId = hexId; + _gst = gst; + _status = ServerStatus.STATUS_DOWN; + } + + /** + * Instantiates a new game server info. + * @param id the id + * @param hexId the hex id + */ + public GameServerInfo(int id, byte[] hexId) { + this(id, hexId, null); + } + + /** + * Sets the id. + * @param id the new id + */ + public void setId(int id) { + _id = id; + } + + /** + * Gets the id. + * @return the id + */ + public int getId() { + return _id; + } + + /** + * Gets the hex id. + * @return the hex id + */ + public byte[] getHexId() { + return _hexId; + } + + public String getName() { + return ServerNameDAO.getServer(_id); + } + + /** + * Sets the authed. + * @param isAuthed the new authed + */ + public void setAuthed(boolean isAuthed) { + _isAuthed = isAuthed; + } + + /** + * Checks if is authed. + * @return true, if is authed + */ + public boolean isAuthed() { + return _isAuthed; + } + + /** + * Sets the game server thread. + * @param gst the new game server thread + */ + public void setGameServerThread(GameServerThread gst) { + _gst = gst; + } + + /** + * Gets the game server thread. + * @return the game server thread + */ + public GameServerThread getGameServerThread() { + return _gst; + } + + /** + * Sets the status. + * @param status the new status + */ + public void setStatus(int status) { + _status = status; + } + + /** + * Gets the status. + * @return the status + */ + public int getStatus() { + return _status; + } + + public String getStatusName() { + return switch (_status) { + case 0 -> "Auto"; + case 1 -> "Good"; + case 2 -> "Normal"; + case 3 -> "Full"; + case 4 -> "Down"; + case 5 -> "GM Only"; + default -> "Unknown"; + }; + } + + /** + * Gets the current player count. + * @return the current player count + */ + public int getCurrentPlayerCount() { + if (_gst == null) { + return 0; + } + return _gst.getPlayerCount(); + } + + /** + * Gets the external host. + * @return the external host + */ + public String getExternalHost() { + try { + return getServerAddress(InetAddress.getByName("0.0.0.0")); + } catch (Exception e) { + + } + return null; + } + + /** + * Gets the port. + * @return the port + */ + public int getPort() { + return _port; + } + + /** + * Sets the port. + * @param port the new port + */ + public void setPort(int port) { + _port = port; + } + + /** + * Sets the max players. + * @param maxPlayers the new max players + */ + public void setMaxPlayers(int maxPlayers) { + _maxPlayers = maxPlayers; + } + + /** + * Gets the max players. + * @return the max players + */ + public int getMaxPlayers() { + return _maxPlayers; + } + + /** + * Checks if is pvp. + * @return true, if is pvp + */ + public boolean isPvp() { + return _isPvp; + } + + /** + * Sets the age limit. + * @param val the new age limit + */ + public void setAgeLimit(int val) { + _ageLimit = val; + } + + /** + * Gets the age limit. + * @return the age limit + */ + public int getAgeLimit() { + return _ageLimit; + } + + /** + * Sets the server type. + * @param val the new server type + */ + public void setServerType(int val) { + _serverType = val; + } + + /** + * Gets the server type. + * @return the server type + */ + public int getServerType() { + return _serverType; + } + + /** + * Sets the showing brackets. + * @param val the new showing brackets + */ + public void setShowingBrackets(boolean val) { + _isShowingBrackets = val; + } + + /** + * Checks if it is showing brackets. + * @return true, if it is showing brackets + */ + public boolean isShowingBrackets() { + return _isShowingBrackets; + } + + /** + * Sets the down. + */ + public void setDown() { + setAuthed(false); + setPort(0); + setGameServerThread(null); + setStatus(ServerStatus.STATUS_DOWN); + } + + /** + * Adds the server address. + * @param subnet the subnet + * @param addr the addr + * @throws UnknownHostException the unknown host exception + */ + public void addServerAddress(String subnet, String addr) throws UnknownHostException { + _addresses.add(new GameServerAddress(subnet, addr)); + } + + /** + * Gets the server address. + * @param addr the addr + * @return the server address + */ + public String getServerAddress(InetAddress addr) { + for (GameServerAddress a : _addresses) { + if (a.equals(addr)) { + return a.getServerAddress(); + } + } + return null; // should not happen + } + + /** + * Gets the server addresses. + * @return the server addresses + */ + public String[] getServerAddresses() { + String[] result = new String[_addresses.size()]; + for (int i = 0; i < result.length; i++) { + result[i] = _addresses.get(i).toString(); + } + + return result; + } + + /** + * Clear server addresses. + */ + public void clearServerAddresses() { + _addresses.clear(); + } + + /** + * The Class GameServerAddress. + */ + private static class GameServerAddress extends IPSubnet { + private final String _serverAddress; + + /** + * Instantiates a new game server address. + * @param subnet the subnet + * @param address the address + * @throws UnknownHostException the unknown host exception + */ + public GameServerAddress(String subnet, String address) throws UnknownHostException { + super(subnet); + _serverAddress = address; + } + + /** + * Gets the server address. + * @return the server address + */ + public String getServerAddress() { + return _serverAddress; + } + + @Override + public String toString() { + return _serverAddress + super.toString(); + } + } + } + + /** + * Gets the single instance of GameServerTable. + * @return single instance of GameServerTable + */ + public static GameServerTable getInstance() { + return SingletonHolder._instance; + } + + /** + * The Class SingletonHolder. + */ + private static class SingletonHolder { + protected static final GameServerTable _instance = new GameServerTable(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerThread.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerThread.java new file mode 100644 index 00000000..9bbd3a5d --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/GameServerThread.java @@ -0,0 +1,320 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.server; +import static com.l2jserver.loginserver.network.loginserverpackets.LoginServerFail.REASON_IP_BANNED; + +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.security.KeyPair; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.network.BaseSendablePacket; +import com.l2jserver.commons.security.crypt.NewCrypt; +import com.l2jserver.commons.util.Util; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.network.L2JGameServerPacketHandler; +import com.l2jserver.loginserver.network.L2JGameServerPacketHandler.GameServerState; +import com.l2jserver.loginserver.network.loginserverpackets.ChangePasswordResponse; +import com.l2jserver.loginserver.network.loginserverpackets.InitLS; +import com.l2jserver.loginserver.network.loginserverpackets.KickPlayer; +import com.l2jserver.loginserver.network.loginserverpackets.LoginServerFail; +import com.l2jserver.loginserver.network.loginserverpackets.RequestCharacters; + +/** + * Game Server thread. + * @author -Wooden- + * @author KenM + * @version 2.6.1.0 + */ +public class GameServerThread extends Thread { + + private static final Logger LOG = LoggerFactory.getLogger(GameServerThread.class); + + private final Socket _connection; + + private InputStream _in; + + private OutputStream _out; + + private final RSAPublicKey _publicKey; + + private final RSAPrivateKey _privateKey; + + private NewCrypt _blowfish; + + private GameServerState _loginConnectionState = GameServerState.CONNECTED; + + private final String _connectionIp; + + private GameServerInfo _gsi; + + /** Authed Clients on a GameServer */ + private final Set _accountsOnGameServer = ConcurrentHashMap.newKeySet(); + + private String _connectionIPAddress; + + @Override + public void run() { + _connectionIPAddress = _connection.getInetAddress().getHostAddress(); + if (GameServerThread.isBannedGameServerIP(_connectionIPAddress)) { + LOG.warn("IP Address {} is on banned IP list.", _connectionIPAddress); + forceClose(REASON_IP_BANNED); + return; + } + + try { + sendPacket(new InitLS(_publicKey.getModulus().toByteArray())); + + int lengthHi; + int lengthLo; + int length; + boolean checksumOk; + for (;;) { + lengthLo = _in.read(); + lengthHi = _in.read(); + length = (lengthHi * 256) + lengthLo; + + if ((lengthHi < 0) || _connection.isClosed()) { + LOG.warn("Login terminated the connection!"); + break; + } + + byte[] data = new byte[length - 2]; + + int receivedBytes = 0; + int newBytes = 0; + int left = length - 2; + while ((newBytes != -1) && (receivedBytes < (length - 2))) { + newBytes = _in.read(data, receivedBytes, left); + receivedBytes = receivedBytes + newBytes; + left -= newBytes; + } + + if (receivedBytes != (length - 2)) { + LOG.warn("Incomplete Packet is sent to the server, closing connection. (LS)"); + break; + } + + // decrypt if we have a key + _blowfish.decrypt(data, 0, data.length); + checksumOk = NewCrypt.verifyChecksum(data); + if (!checksumOk) { + LOG.warn("Incorrect packet checksum, closing connection. (LS)"); + return; + } + + if (server().isDebug()) { + LOG.warn("[C]" + System.lineSeparator() + Util.printData(data)); + } + + L2JGameServerPacketHandler.handlePacket(data, this); + } + } catch (IOException ex) { + final var serverName = (getServerId() != -1 ? "[" + getServerId() + "] " + ServerNameDAO.getServer(getServerId()) : "(" + _connectionIPAddress + ")"); + LOG.warn("Game Server {} lost connection!", serverName); + broadcastToTelnet("Game Server " + serverName + " lost connection!"); + } finally { + if (isAuthed()) { + _gsi.setDown(); + + LOG.info("Server {}[{}] is now disconnected.", ServerNameDAO.getServer(getServerId()), getServerId()); + } + LoginServer.getInstance().getGameServerListener().removeGameServer(this); + LoginServer.getInstance().getGameServerListener().removeFloodProtection(_connectionIp); + } + } + + public boolean hasAccountOnGameServer(String account) { + return _accountsOnGameServer.contains(account); + } + + public int getPlayerCount() { + return _accountsOnGameServer.size(); + } + + /** + * Attaches a GameServerInfo to this Thread
+ *
    + *
  • Updates the GameServerInfo values based on GameServerAuth packet
  • + *
  • Sets the GameServerInfo as Authed
  • + *
+ * @param gsi The GameServerInfo to be attached. + * @param port the port + * @param hosts the hosts + * @param maxPlayers the maximum amount of players + */ + public void attachGameServerInfo(GameServerInfo gsi, int port, String[] hosts, int maxPlayers) { + setGameServerInfo(gsi); + gsi.setGameServerThread(this); + gsi.setPort(port); + setGameHosts(hosts); + gsi.setMaxPlayers(maxPlayers); + gsi.setAuthed(true); + } + + public void forceClose(int reason) { + sendPacket(new LoginServerFail(reason)); + + try { + _connection.close(); + } catch (IOException ex) { + LOG.debug("Failed disconnecting banned server, server already disconnected."); + } + } + + public static boolean isBannedGameServerIP(String ipAddress) { + return false; + } + + public GameServerThread(Socket con) { + _connection = con; + _connectionIp = con.getInetAddress().getHostAddress(); + try { + _in = _connection.getInputStream(); + _out = new BufferedOutputStream(_connection.getOutputStream()); + } catch (IOException ex) { + LOG.warn("There has been an error creating a connection!", ex); + } + + KeyPair pair = GameServerTable.getInstance().getKeyPair(); + _privateKey = (RSAPrivateKey) pair.getPrivate(); + _publicKey = (RSAPublicKey) pair.getPublic(); + _blowfish = new NewCrypt("_;v.]05-31!|+-%xT!^[$\00"); + setName(getClass().getSimpleName() + "-" + threadId() + "@" + _connectionIp); + start(); + } + + public void sendPacket(BaseSendablePacket sl) { + try { + byte[] data = sl.getContent(); + NewCrypt.appendChecksum(data); + if (server().isDebug()) { + LOG.info("[S] {}:{}{}", sl.getClass().getSimpleName(), System.lineSeparator(), Util.printData(data)); + } + _blowfish.crypt(data, 0, data.length); + + int len = data.length + 2; + synchronized (_out) { + _out.write(len & 0xff); + _out.write((len >> 8) & 0xff); + _out.write(data); + _out.flush(); + } + } catch (IOException ex) { + LOG.error("There has been an error while sending packet {}!", sl.getClass().getSimpleName(), ex); + } + } + + public void broadcastToTelnet(String msg) { + if (LoginServer.getInstance().getStatusServer() != null) { + LoginServer.getInstance().getStatusServer().sendMessageToTelnets(msg); + } + } + + public void kickPlayer(String account) { + sendPacket(new KickPlayer(account)); + } + + public void requestCharacters(String account) { + sendPacket(new RequestCharacters(account)); + } + + public void ChangePasswordResponse(byte successful, String characterName, String msgToSend) { + sendPacket(new ChangePasswordResponse(successful, characterName, msgToSend)); + } + + public void setGameHosts(String[] hosts) { + LOG.info("Updated game server {}[{}] IPs.", ServerNameDAO.getServer(getServerId()), getServerId()); + + _gsi.clearServerAddresses(); + for (int i = 0; i < hosts.length; i += 2) { + try { + _gsi.addServerAddress(hosts[i], hosts[i + 1]); + } catch (Exception ex) { + LOG.warn("There has been an error resolving host name {}!", hosts[i], ex); + } + } + + for (String s : _gsi.getServerAddresses()) { + LOG.info(s); + } + } + + public boolean isAuthed() { + if (getGameServerInfo() == null) { + return false; + } + return getGameServerInfo().isAuthed(); + } + + public void setGameServerInfo(GameServerInfo gsi) { + _gsi = gsi; + } + + public GameServerInfo getGameServerInfo() { + return _gsi; + } + + public String getConnectionIpAddress() { + return _connectionIPAddress; + } + + public int getServerId() { + if (getGameServerInfo() != null) { + return getGameServerInfo().getId(); + } + return -1; + } + + public RSAPrivateKey getPrivateKey() { + return _privateKey; + } + + public void SetBlowFish(NewCrypt blowfish) { + _blowfish = blowfish; + } + + public void addAccountOnGameServer(String account) { + _accountsOnGameServer.add(account); + } + + public void removeAccountOnGameServer(String account) { + _accountsOnGameServer.remove(account); + } + + public GameServerState getLoginConnectionState() { + return _loginConnectionState; + } + + public void setLoginConnectionState(GameServerState state) { + _loginConnectionState = state; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginController.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginController.java new file mode 100644 index 00000000..94783ff0 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginController.java @@ -0,0 +1,550 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.server; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.security.spec.RSAKeyGenParameterSpec.F4; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.security.KeyPairGenerator; +import java.security.MessageDigest; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.RSAKeyGenParameterSpec; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import javax.crypto.Cipher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.database.ConnectionFactory; +import com.l2jserver.commons.util.Rnd; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.model.AccountInfo; +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.loginserver.network.gameserverpackets.ServerStatus; +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; +import com.l2jserver.loginserver.security.ScrambledKeyPair; + +/** + * Login Controller. + * @author Zoey76 + * @version 2.6.1.0 + */ +public class LoginController { + + private static final Logger LOG = LoggerFactory.getLogger(LoginController.class); + + /** Time before kicking the client if he didn't log yet */ + public static final int LOGIN_TIMEOUT = 60 * 1000; + + /** Authed Clients on LoginServer */ + protected final Map _loginServerClients = new ConcurrentHashMap<>(); + + private final Map _failedLoginAttempts = new ConcurrentHashMap<>(); + private final Map _bannedIps = new ConcurrentHashMap<>(); + + protected final ScrambledKeyPair[] _keyPairs; + + protected byte[][] _blowfishKeys; + private static final int BLOWFISH_KEYS = 20; + + // SQL Queries + private static final String USER_INFO_SELECT = "SELECT login, password, IF(? > value OR value IS NULL, accessLevel, -1) AS accessLevel, lastServer FROM accounts LEFT JOIN (account_data) ON (account_data.account_name=accounts.login AND account_data.var=\"ban_temp\") WHERE login=?"; + private static final String AUTO_CREATE_ACCOUNTS_INSERT = "INSERT INTO accounts (login, password, lastactive, accessLevel, lastIP) values (?, ?, ?, ?, ?)"; + private static final String ACCOUNT_INFO_UPDATE = "UPDATE accounts SET lastactive = ?, lastIP = ? WHERE login = ?"; + private static final String ACCOUNT_LAST_SERVER_UPDATE = "UPDATE accounts SET lastServer = ? WHERE login = ?"; + private static final String ACCOUNT_ACCESS_LEVEL_UPDATE = "UPDATE accounts SET accessLevel = ? WHERE login = ?"; + private static final String ACCOUNT_IPS_UPDATE = "UPDATE accounts SET pcIp = ?, hop1 = ?, hop2 = ?, hop3 = ?, hop4 = ? WHERE login = ?"; + private static final String ACCOUNT_IPAUTH_SELECT = "SELECT * FROM accounts_ipauth WHERE login = ?"; + + private LoginController() { + LOG.info("Loading Login Controller..."); + + _keyPairs = new ScrambledKeyPair[10]; + + try { + final var keygen = KeyPairGenerator.getInstance("RSA"); + final var spec = new RSAKeyGenParameterSpec(1024, F4); + keygen.initialize(spec); + for (int i = 0; i < 10; i++) { + _keyPairs[i] = new ScrambledKeyPair(keygen.generateKeyPair()); + } + + testCipher((RSAPrivateKey) _keyPairs[0].getPair().getPrivate()); + + LOG.info("Cached 10 KeyPairs for RSA communication."); + } catch (Exception ex) { + LOG.error("There has been an error loading the key pairs!", ex); + } + + // Store keys for blowfish communication + generateBlowFishKeys(); + + final var purge = new PurgeThread(); + purge.setDaemon(true); + purge.start(); + } + + /** + * This is mostly to force the initialization of the Crypto Implementation, avoiding it being done on runtime when its first needed.
+ * In short, it avoids the worst-case execution time on runtime by doing it on loading. + * @param key Any private RSA Key just for testing purposes. + */ + private void testCipher(RSAPrivateKey key) throws Exception { + // avoid worst-case execution, KenM + Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding"); + rsaCipher.init(Cipher.DECRYPT_MODE, key); + } + + private void generateBlowFishKeys() { + _blowfishKeys = new byte[BLOWFISH_KEYS][16]; + + for (int i = 0; i < BLOWFISH_KEYS; i++) { + for (int j = 0; j < _blowfishKeys[i].length; j++) { + _blowfishKeys[i][j] = (byte) (Rnd.nextInt(255) + 1); + } + } + LOG.info("Stored {} keys for Blowfish communication.", _blowfishKeys.length); + } + + public byte[] getBlowfishKey() { + return _blowfishKeys[(int) (Math.random() * BLOWFISH_KEYS)]; + } + + public SessionKey assignSessionKeyToClient(String account, L2LoginClient client) { + SessionKey key; + + key = new SessionKey(Rnd.nextInt(), Rnd.nextInt(), Rnd.nextInt(), Rnd.nextInt()); + _loginServerClients.put(account, client); + return key; + } + + public void removeAuthedLoginClient(String account) { + if (account == null) { + return; + } + _loginServerClients.remove(account); + } + + public L2LoginClient getAuthedClient(String account) { + return _loginServerClients.get(account); + } + + public AccountInfo retrieveAccountInfo(InetAddress clientAddr, String login, String password) { + return retrieveAccountInfo(clientAddr, login, password, true); + } + + private void recordFailedLoginAttempt(InetAddress addr) { + final var failedLoginAttempts = _failedLoginAttempts.getOrDefault(addr, 0) + 1; + _failedLoginAttempts.put(addr, failedLoginAttempts); + + if (failedLoginAttempts >= server().getLoginTryBeforeBan()) { + addBanForAddress(addr, server().getLoginBlockAfterBan() * 1000); + // we need to clear the failed login attempts here, so after the ip ban is over the client has another 5 attempts + clearFailedLoginAttempts(addr); + LOG.warn("Added banned address {}, too many login attempts!", addr.getHostAddress()); + } + } + + private void clearFailedLoginAttempts(InetAddress addr) { + _failedLoginAttempts.remove(addr); + } + + private AccountInfo retrieveAccountInfo(InetAddress addr, String login, String password, boolean autoCreateIfEnabled) { + try { + final var md = MessageDigest.getInstance("SHA"); + final var raw = password.getBytes(UTF_8); + final var hashBase64 = Base64.getEncoder().encodeToString(md.digest(raw)); + + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(USER_INFO_SELECT)) { + ps.setString(1, Long.toString(System.currentTimeMillis())); + ps.setString(2, login); + try (var rs = ps.executeQuery()) { + if (rs.next()) { + if (server().isDebug()) { + LOG.info("Account {} exists.", login); + } + + final var info = new AccountInfo(rs.getString("login"), rs.getString("password"), rs.getInt("accessLevel"), rs.getInt("lastServer")); + if (!info.checkPassHash(hashBase64)) { + recordFailedLoginAttempt(addr); + return null; + } + + clearFailedLoginAttempts(addr); + return info; + } + } + } + + if (!autoCreateIfEnabled || !server().autoCreateAccounts()) { + // account does not exist and auto create account is not desired + recordFailedLoginAttempt(addr); + return null; + } + + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(AUTO_CREATE_ACCOUNTS_INSERT)) { + ps.setString(1, login); + ps.setString(2, hashBase64); + ps.setLong(3, System.currentTimeMillis()); + ps.setInt(4, server().autoCreateAccountsAccessLevel()); + ps.setString(5, addr.getHostAddress()); + ps.execute(); + } catch (Exception ex) { + LOG.warn("There has been an error auto-creating the account {}!", login, ex); + return null; + } + + LOG.info("Auto-created account {}.", login); + return retrieveAccountInfo(addr, login, password, false); + } catch (Exception ex) { + LOG.warn("There has been an error getting account info for {}!", login, ex); + return null; + } + } + + public AuthLoginResult tryCheckinAccount(L2LoginClient client, InetAddress address, AccountInfo info) { + if (info.getAccessLevel() < 0) { + if (info.getAccessLevel() == server().autoCreateAccountsAccessLevel()) { + return AuthLoginResult.ACCOUNT_INACTIVE; + } + return AuthLoginResult.ACCOUNT_BANNED; + } + + AuthLoginResult ret = AuthLoginResult.INVALID_PASSWORD; + // check auth + if (canCheckIn(client, address, info)) { + // login was successful, verify presence on game servers + ret = AuthLoginResult.ALREADY_ON_GS; + if (!isAccountInAnyGameServer(info.getLogin())) { + // account isn't on any GS verify LS itself + ret = AuthLoginResult.ALREADY_ON_LS; + + if (_loginServerClients.putIfAbsent(info.getLogin(), client) == null) { + ret = AuthLoginResult.AUTH_SUCCESS; + } + } + } + return ret; + } + + /** + * Adds the address to the ban list of the login server, with the given end time in milliseconds. + * @param address The Address to be banned. + * @param expiration Timestamp in milliseconds when this ban expires + * @throws UnknownHostException if the address is invalid. + */ + public void addBanForAddress(String address, long expiration) throws Exception { + _bannedIps.putIfAbsent(InetAddress.getByName(address), expiration); + } + + /** + * Adds the address to the ban list of the login server, with the given duration. + * @param address The Address to be banned. + * @param duration is milliseconds + */ + public void addBanForAddress(InetAddress address, long duration) { + _bannedIps.putIfAbsent(address, System.currentTimeMillis() + duration); + } + + public boolean isBannedAddress(InetAddress address) throws Exception { + String[] parts = address.getHostAddress().split("\\."); + Long bi = _bannedIps.get(address); + if (bi == null) { + bi = _bannedIps.get(InetAddress.getByName(parts[0] + "." + parts[1] + "." + parts[2] + ".0")); + } + if (bi == null) { + bi = _bannedIps.get(InetAddress.getByName(parts[0] + "." + parts[1] + ".0.0")); + } + if (bi == null) { + bi = _bannedIps.get(InetAddress.getByName(parts[0] + ".0.0.0")); + } + if (bi != null) { + if ((bi > 0) && (bi < System.currentTimeMillis())) { + _bannedIps.remove(address); + LOG.info("Removed expired IP address ban {}.", address.getHostAddress()); + return false; + } + return true; + } + return false; + } + + public Map getBannedIps() { + return _bannedIps; + } + + /** + * Remove the specified address from the ban list + * @param address The address to be removed from the ban list + * @return true if the ban was removed, false if there was no ban for this ip + */ + public boolean removeBanForAddress(InetAddress address) { + return _bannedIps.remove(address) != null; + } + + /** + * Remove the specified address from the ban list + * @param address The address to be removed from the ban list + * @return true if the ban was removed, false if there was no ban for this ip or the address was invalid. + */ + public boolean removeBanForAddress(String address) { + try { + return this.removeBanForAddress(InetAddress.getByName(address)); + } catch (Exception e) { + return false; + } + } + + public SessionKey getKeyForAccount(String account) { + L2LoginClient client = _loginServerClients.get(account); + if (client != null) { + return client.getSessionKey(); + } + return null; + } + + public boolean isAccountInAnyGameServer(String account) { + Collection serverList = GameServerTable.getInstance().getRegisteredGameServers().values(); + for (GameServerInfo gsi : serverList) { + GameServerThread gst = gsi.getGameServerThread(); + if ((gst != null) && gst.hasAccountOnGameServer(account)) { + return true; + } + } + return false; + } + + public GameServerInfo getAccountOnGameServer(String account) { + Collection serverList = GameServerTable.getInstance().getRegisteredGameServers().values(); + for (GameServerInfo gsi : serverList) { + GameServerThread gst = gsi.getGameServerThread(); + if ((gst != null) && gst.hasAccountOnGameServer(account)) { + return gsi; + } + } + return null; + } + + public void getCharactersOnAccount(String account) { + Collection serverList = GameServerTable.getInstance().getRegisteredGameServers().values(); + for (GameServerInfo gsi : serverList) { + if (gsi.isAuthed()) { + gsi.getGameServerThread().requestCharacters(account); + } + } + } + + public boolean isLoginPossible(L2LoginClient client, int serverId) { + GameServerInfo gsi = GameServerTable.getInstance().getRegisteredGameServerById(serverId); + int access = client.getAccessLevel(); + if ((gsi != null) && gsi.isAuthed()) { + boolean loginOk = ((gsi.getCurrentPlayerCount() < gsi.getMaxPlayers()) && (gsi.getStatus() != ServerStatus.STATUS_GM_ONLY)) || (access > 0); + + if (loginOk && (client.getLastServer() != serverId)) { + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(ACCOUNT_LAST_SERVER_UPDATE)) { + ps.setInt(1, serverId); + ps.setString(2, client.getAccount()); + ps.executeUpdate(); + } catch (Exception ex) { + LOG.warn("There has been an error setting last server for account {}!", client.getAccount(), ex); + } + } + return loginOk; + } + return false; + } + + public void setAccountAccessLevel(String account, int banLevel) { + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(ACCOUNT_ACCESS_LEVEL_UPDATE)) { + ps.setInt(1, banLevel); + ps.setString(2, account); + ps.executeUpdate(); + } catch (Exception ex) { + LOG.warn("There has been an error setting account level for account {}!", account, ex); + } + } + + public void setAccountLastTracert(String account, String pcIp, String hop1, String hop2, String hop3, String hop4) { + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(ACCOUNT_IPS_UPDATE)) { + ps.setString(1, pcIp); + ps.setString(2, hop1); + ps.setString(3, hop2); + ps.setString(4, hop3); + ps.setString(5, hop4); + ps.setString(6, account); + ps.executeUpdate(); + } catch (Exception ex) { + LOG.warn("There has been an error setting last tracert for account {}!", account, ex); + } + } + + public void setCharactersOnServer(String account, int charsNum, long[] timeToDel, int serverId) { + final var client = _loginServerClients.get(account); + if (client == null) { + return; + } + + if (charsNum > 0) { + client.setCharsOnServ(serverId, charsNum); + } + + if (timeToDel.length > 0) { + client.serCharsWaitingDelOnServ(serverId, timeToDel); + } + } + + /** + * This method returns one of the cached {@link ScrambledKeyPair ScrambledKeyPairs} for communication with Login Clients. + * @return a scrambled keypair + */ + public ScrambledKeyPair getScrambledRSAKeyPair() { + return _keyPairs[Rnd.nextInt(10)]; + } + + /** + * @param client the client + * @param address client host address + * @param info the account info to check in + * @return true when ok to check in, false otherwise + */ + public boolean canCheckIn(L2LoginClient client, InetAddress address, AccountInfo info) { + try { + List ipWhiteList = new ArrayList<>(); + List ipBlackList = new ArrayList<>(); + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(ACCOUNT_IPAUTH_SELECT)) { + ps.setString(1, info.getLogin()); + try (var rs = ps.executeQuery()) { + while (rs.next()) { + final var ip = rs.getString("ip"); + if (!isValidIPAddress(ip)) { + continue; + } + + final var type = rs.getString("type"); + if (type.equals("allow")) { + ipWhiteList.add(InetAddress.getByName(ip)); + } else if (type.equals("deny")) { + ipBlackList.add(InetAddress.getByName(ip)); + } + } + } + } + + // Check IP + if (!ipWhiteList.isEmpty() || !ipBlackList.isEmpty()) { + if (!ipWhiteList.isEmpty() && !ipWhiteList.contains(address)) { + LOG.warn("Account checkin attempt from address {} not present on whitelist for account {}!", address.getHostAddress(), info.getLogin()); + return false; + } + + if (!ipBlackList.isEmpty() && ipBlackList.contains(address)) { + LOG.warn("Account checkin attempt from address {} on blacklist for account {}!", address.getHostAddress(), info.getLogin()); + return false; + } + } + + client.setAccessLevel(info.getAccessLevel()); + client.setLastServer(info.getLastServer()); + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement(ACCOUNT_INFO_UPDATE)) { + ps.setLong(1, System.currentTimeMillis()); + ps.setString(2, address.getHostAddress()); + ps.setString(3, info.getLogin()); + ps.execute(); + } + return true; + } catch (Exception ex) { + LOG.warn("There has been an error logging in!", ex); + return false; + } + } + + public boolean isValidIPAddress(String ipAddress) { + String[] parts = ipAddress.split("\\."); + if (parts.length != 4) { + return false; + } + + for (String s : parts) { + int i = Integer.parseInt(s); + if ((i < 0) || (i > 255)) { + return false; + } + } + return true; + } + + class PurgeThread extends Thread { + public PurgeThread() { + setName("PurgeThread"); + } + + @Override + public void run() { + while (!isInterrupted()) { + for (L2LoginClient client : _loginServerClients.values()) { + if (client == null) { + continue; + } + if ((client.getConnectionStartTime() + LOGIN_TIMEOUT) < System.currentTimeMillis()) { + client.close(LoginFailReason.REASON_ACCESS_FAILED); + } + } + + try { + Thread.sleep(LOGIN_TIMEOUT / 2); + } catch (InterruptedException e) { + return; + } + } + } + } + + public enum AuthLoginResult { + INVALID_PASSWORD, + ACCOUNT_INACTIVE, + ACCOUNT_BANNED, + ALREADY_ON_LS, + ALREADY_ON_GS, + AUTH_SUCCESS + } + + public static LoginController getInstance() { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder { + protected static final LoginController INSTANCE = new LoginController(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginServer.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginServer.java new file mode 100644 index 00000000..fa2b03c3 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/LoginServer.java @@ -0,0 +1,221 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.database; +import static com.l2jserver.loginserver.config.Configuration.email; +import static com.l2jserver.loginserver.config.Configuration.mmo; +import static com.l2jserver.loginserver.config.Configuration.server; +import static com.l2jserver.loginserver.config.Configuration.telnet; + +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.net.InetAddress; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.UPnPService; +import com.l2jserver.commons.database.ConnectionFactory; +import com.l2jserver.loginserver.mail.MailSystem; +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.loginserver.network.L2LoginPacketHandler; +import com.l2jserver.loginserver.status.Status; +import com.l2jserver.mmocore.SelectorConfig; +import com.l2jserver.mmocore.SelectorThread; + +/** + * Login Server. + * @author KenM + * @author Zoey76 + * @version 2.6.1.0 + */ +public final class LoginServer { + + private static final Logger LOG = LoggerFactory.getLogger(LoginServer.class); + + private static final String BANNED_IPS = "/config/banned_ip.cfg"; + + private static LoginServer _instance; + + private GameServerListener _gameServerListener; + + private SelectorThread _selectorThread; + + private Status _statusServer; + + public static void main(String[] args) { + new LoginServer(); + } + + public static LoginServer getInstance() { + return _instance; + } + + private LoginServer() { + _instance = this; + + // Prepare Database + ConnectionFactory.builder() // + .withUrl(database().getURL()) // + .withUser(database().getUser()) // + .withPassword(database().getPassword()) // + .withMaxIdleTime(database().getMaxIdleTime()) // + .withMaxPoolSize(database().getMaxConnections()) // + .build(); + + LoginController.getInstance(); + + GameServerTable.getInstance(); + + loadBanFile(); + + if (email().isEnabled()) { + MailSystem.getInstance(); + } + + final SelectorConfig sc = new SelectorConfig(); + sc.MAX_READ_PER_PASS = mmo().getMaxReadPerPass(); + sc.MAX_SEND_PER_PASS = mmo().getMaxSendPerPass(); + sc.SLEEP_TIME = mmo().getSleepTime(); + sc.HELPER_BUFFER_COUNT = mmo().getHelperBufferCount(); + + final L2LoginPacketHandler loginPacketHandler = new L2LoginPacketHandler(); + final SelectorHelper selectorHelper = new SelectorHelper(); + try { + _selectorThread = new SelectorThread<>(sc, selectorHelper, loginPacketHandler, selectorHelper, selectorHelper); + } catch (Exception ex) { + LOG.error("Failed to open Selector!", ex); + System.exit(1); + } + + try { + _gameServerListener = new GameServerListener(); + _gameServerListener.start(); + LOG.info("Listening for game servers on {}:{}.", server().getGameServerHost(), server().getGameServerPort()); + } catch (Exception ex) { + LOG.error("Failed to start the Game Server Listener!", ex); + System.exit(1); + } + + if (telnet().isEnabled()) { + try { + _statusServer = new Status(); + _statusServer.start(); + } catch (Exception ex) { + LOG.warn("Failed to start the Telnet Server!", ex); + } + } else { + LOG.info("Telnet server is currently disabled."); + } + + InetAddress bindAddress = null; + if (!server().getHost().equals("*")) { + try { + bindAddress = InetAddress.getByName(server().getGameServerHost()); + } catch (Exception ex) { + LOG.warn("The Login Server bind address is invalid, using all available IPs!", ex); + } + } + try { + _selectorThread.openServerSocket(bindAddress, server().getPort()); + _selectorThread.start(); + LOG.info("Login Server is now listening on {}:{}.", server().getHost(), server().getPort()); + } catch (Exception ex) { + LOG.error("Failed to open server socket!", ex); + System.exit(1); + } + + if (server().isUPnPEnabled()) { + UPnPService.getInstance().load(server().getPort(), "L2J Login Server"); + } + } + + public Status getStatusServer() { + return _statusServer; + } + + public GameServerListener getGameServerListener() { + return _gameServerListener; + } + + private void loadBanFile() { + try (var fis = getClass().getResourceAsStream(BANNED_IPS); + var is = new InputStreamReader(fis); + var lnr = new LineNumberReader(is)) { + lnr.lines() // + .map(String::trim) // + .filter(l -> !l.isEmpty() && (l.charAt(0) != '#')) // + .forEach(line -> { + String[] parts = line.split("#", 2); // address[ duration][ # comments] + line = parts[0]; + parts = line.split("\\s+"); // durations might be aligned via multiple spaces + String address = parts[0]; + long duration = 0; + + if (parts.length > 1) { + try { + duration = Long.parseLong(parts[1]); + } catch (Exception ex) { + LOG.warn("Incorrect ban duration {} on line {} on file {}!", parts[1], lnr.getLineNumber(), BANNED_IPS, ex); + return; + } + } + + try { + LoginController.getInstance().addBanForAddress(address, duration); + } catch (Exception ex) { + LOG.warn("Invalid address {} on line {} on file {}!", address, lnr.getLineNumber(), BANNED_IPS, ex); + } + }); + } catch (Exception ex) { + LOG.warn("Error while reading the bans file {}!", BANNED_IPS, ex); + } + LOG.info("Loaded {} banned IPs.", LoginController.getInstance().getBannedIps().size()); + + if (server().isLoginRestartEnabled()) { + final var restartLoginServer = new LoginServerRestart(); + restartLoginServer.setDaemon(true); + restartLoginServer.start(); + LOG.info("Scheduled restart after {} hours.", server().getLoginRestartTime()); + } + } + + class LoginServerRestart extends Thread { + public LoginServerRestart() { + setName("LoginServerRestart"); + } + + @Override + public void run() { + while (!isInterrupted()) { + try { + Thread.sleep(server().getLoginRestartTime() * 3600000); + } catch (InterruptedException e) { + return; + } + shutdown(true); + } + } + } + + public void shutdown(boolean restart) { + Runtime.getRuntime().exit(restart ? 2 : 0); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/SelectorHelper.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/SelectorHelper.java new file mode 100644 index 00000000..8b0426fd --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/SelectorHelper.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import java.nio.channels.SocketChannel; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.util.IPv4Filter; +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.loginserver.network.serverpackets.Init; +import com.l2jserver.mmocore.AcceptFilter; +import com.l2jserver.mmocore.ClientFactory; +import com.l2jserver.mmocore.MMOConnection; +import com.l2jserver.mmocore.MMOExecutor; +import com.l2jserver.mmocore.ReceivablePacket; + +/** + * Selector Helper. + * @author KenM + * @author Zoey76 + * @version 2.6.1.0 + */ +public class SelectorHelper implements MMOExecutor, ClientFactory, AcceptFilter { + + private static final Logger LOG = LoggerFactory.getLogger(SelectorHelper.class); + + private final ThreadPoolExecutor _generalPacketsThreadPool; + + private final IPv4Filter _ipv4filter; + + public SelectorHelper() { + _generalPacketsThreadPool = new ThreadPoolExecutor(4, 6, 15L, SECONDS, new LinkedBlockingQueue<>()); + _ipv4filter = new IPv4Filter(); + } + + @Override + public void execute(ReceivablePacket packet) { + _generalPacketsThreadPool.execute(packet); + } + + @Override + public L2LoginClient create(MMOConnection con) { + L2LoginClient client = new L2LoginClient(con); + client.sendPacket(new Init(client)); + return client; + } + + @Override + public boolean accept(SocketChannel sc) { + try { + return _ipv4filter.accept(sc) && !LoginController.getInstance().isBannedAddress(sc.socket().getInetAddress()); + } catch (Exception ex) { + LOG.error("Invalid address {}!", sc.socket().getInetAddress(), ex); + } + return false; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/SessionKey.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/SessionKey.java new file mode 100644 index 00000000..378c7591 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/SessionKey.java @@ -0,0 +1,70 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver; + +import static com.l2jserver.loginserver.config.Configuration.server; + +/** + * This class is used to represent session keys used by the client to authenticate in the game server.
+ * A SessionKey is made up of two 8 bytes keys. One is sent in the {@link com.l2jserver.loginserver.network.serverpackets.LoginOk#LoginOk} packet and the other is sent in {@link com.l2jserver.loginserver.network.serverpackets.PlayOk#PlayOk} + * @author -Wooden- + * @version 2.6.1.0 + */ +public class SessionKey { + public final int playOkID1; + public final int playOkID2; + public final int loginOkID1; + public final int loginOkID2; + + public SessionKey(int loginOK1, int loginOK2, int playOK1, int playOK2) { + playOkID1 = playOK1; + playOkID2 = playOK2; + loginOkID1 = loginOK1; + loginOkID2 = loginOK2; + } + + @Override + public String toString() { + return "PlayOk: " + playOkID1 + " " + playOkID2 + " LoginOk:" + loginOkID1 + " " + loginOkID2; + } + + public boolean checkLoginPair(int loginOk1, int loginOk2) { + return (loginOkID1 == loginOk1) && (loginOkID2 == loginOk2); + } + + /** + * Only checks the PlayOk part of the session key if server doesn't show the license when player logs in. + * @param other the other session key to validate + * @return true if keys are equal. + */ + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof SessionKey key)) { + return false; + } + // when server doesn't show license it doesn't send the LoginOk packet, client doesn't have this part of the key then. + if (server().showLicense()) { + return ((playOkID1 == key.playOkID1) && (loginOkID1 == key.loginOkID1) && (playOkID2 == key.playOkID2) && (loginOkID2 == key.loginOkID2)); + } + return ((playOkID1 == key.playOkID1) && (playOkID2 == key.playOkID2)); + } +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/Configuration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/Configuration.java new file mode 100644 index 00000000..dfa814c2 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/Configuration.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import org.aeonbits.owner.ConfigFactory; + +/** + * Configuration. + * @author Zoey76 + * @version 2.6.1.0 + */ +public class Configuration { + + private static final EmailConfiguration email = ConfigFactory.create(EmailConfiguration.class); + + private static final ServerConfiguration server = ConfigFactory.create(ServerConfiguration.class); + + private static final DatabaseConfiguration database = ConfigFactory.create(DatabaseConfiguration.class); + + private static final MMOConfiguration mmo = ConfigFactory.create(MMOConfiguration.class); + + private static final TelnetConfiguration telnet = ConfigFactory.create(TelnetConfiguration.class); + + private Configuration() { + // Do nothing. + } + + public static EmailConfiguration email() { + return email; + } + + public static ServerConfiguration server() { + return server; + } + + public static DatabaseConfiguration database() { + return database; + } + + public static MMOConfiguration mmo() { + return mmo; + } + + public static TelnetConfiguration telnet() { + return telnet; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/DatabaseConfiguration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/DatabaseConfiguration.java new file mode 100644 index 00000000..0f8e4a3d --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/DatabaseConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.aeonbits.owner.Config.HotReloadType.ASYNC; +import static org.aeonbits.owner.Config.LoadType.MERGE; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.HotReload; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +/** + * Database Configuration. + * @author Zoey76 + * @version 2.6.1.5 + */ +@Sources({ + "file:${L2J_HOME}/custom/login/config/database.properties", + "file:./config/database.properties", + "classpath:config/database.properties" +}) +@LoadPolicy(MERGE) +@HotReload(value = 20, unit = MINUTES, type = ASYNC) +public interface DatabaseConfiguration extends Config { + + @Key("URL") + String getURL(); + + @Key("User") + String getUser(); + + @Key("Password") + String getPassword(); + + @Key("MaxConnections") + Integer getMaxConnections(); + + @Key("MaxIdleTime") + Integer getMaxIdleTime(); +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/EmailConfiguration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/EmailConfiguration.java new file mode 100644 index 00000000..891f41ab --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/EmailConfiguration.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.aeonbits.owner.Config.HotReloadType.ASYNC; +import static org.aeonbits.owner.Config.LoadType.MERGE; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.HotReload; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +/** + * Email Configuration. + * @author Zoey76 + * @version 2.6.1.1 + */ +@Sources({ + "file:${L2J_HOME}/custom/login/config/email.properties", + "file:./config/email.properties", + "classpath:config/email.properties" +}) +@LoadPolicy(MERGE) +@HotReload(value = 20, unit = MINUTES, type = ASYNC) +public interface EmailConfiguration extends Config { + + @Key("EnableEmail") + boolean isEnabled(); + + @Key("ServerName") + String getServerName(); + + @Key("ServerEmail") + String getServerEmail(); + + @Key("SelectQuery") + String getSelectQuery(); + + @Key("DatabaseField") + String getDatabaseField(); + + @Key("Host") + String getHost(); + + @Key("Port") + int getPort(); + + @Key("SmtpAuthRequired") + boolean isSmtpAuthRequired(); + + @Key("SmtpFactory") + String getSmtpFactory(); + + @Key("SmtpFactoryCallback") + boolean smtpFactoryCallback(); + + @Key("SmtpUsername") + String getSmtpUsername(); + + @Key("SmtpPassword") + String getSmtpPassword(); + + @Key("SystemAddress") + String getSystemAddress(); +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/MMOConfiguration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/MMOConfiguration.java new file mode 100644 index 00000000..348fcaf3 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/MMOConfiguration.java @@ -0,0 +1,58 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.aeonbits.owner.Config.HotReloadType.ASYNC; +import static org.aeonbits.owner.Config.LoadType.MERGE; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.HotReload; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +/** + * MMOConfiguration. + * @author Zoey76 + * @version 2.6.1.1 + */ +@Sources({ + "file:${L2J_HOME}/custom/login/config/mmo.properties", + "file:./config/mmo.properties", + "classpath:config/mmo.properties" +}) +@LoadPolicy(MERGE) +@HotReload(value = 20, unit = MINUTES, type = ASYNC) +public interface MMOConfiguration extends Config { + + @Key("SleepTime") + int getSleepTime(); + + @Key("MaxSendPerPass") + int getMaxSendPerPass(); + + @Key("MaxReadPerPass") + int getMaxReadPerPass(); + + @Key("HelperBufferCount") + int getHelperBufferCount(); + + @Key("TcpNoDelay") + boolean isTcpNoDelay(); +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/ServerConfiguration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/ServerConfiguration.java new file mode 100644 index 00000000..7dde5d17 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/ServerConfiguration.java @@ -0,0 +1,111 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.aeonbits.owner.Config.HotReloadType.ASYNC; +import static org.aeonbits.owner.Config.LoadType.MERGE; + +import java.util.Set; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.HotReload; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +/** + * Server Configuration. + * @author Zoey76 + * @version 2.6.1.1 + */ +@Sources({ + "file:${L2J_HOME}/custom/login/config/server.properties", + "file:./config/server.properties", + "classpath:config/server.properties" +}) +@LoadPolicy(MERGE) +@HotReload(value = 20, unit = MINUTES, type = ASYNC) +public interface ServerConfiguration extends Config { + + @Key("EnableUPnP") + boolean isUPnPEnabled(); + + @Key("Host") + String getHost(); + + @Key("Port") + int getPort(); + + @Key("GameServerHost") + String getGameServerHost(); + + @Key("GameServerPort") + int getGameServerPort(); + + @Key("ServerVersions") + Set getServerVersions(); + + @Key("LoginTryBeforeBan") + int getLoginTryBeforeBan(); + + @Key("LoginBlockAfterBan") + int getLoginBlockAfterBan(); + + @Key("AcceptNewGameServer") + boolean isAcceptNetGameServer(); + + @Key("EnableFloodProtection") + boolean isFloodProtectionEnabled(); + + @Key("FastConnectionLimit") + int getFastConnectionLimit(); + + @Key("NormalConnectionTime") + int getNormalConnectionTime(); + + @Key("FastConnectionTime") + int getFastConnectionTime(); + + @Key("MaxConnectionPerIP") + int getMaxConnectionPerIP(); + + @Key("AccountInactiveAccessLevel") + int getAccountInactiveAccessLevel(); + + @Key("ShowLicence") + boolean showLicense(); + + @Key("AutoCreateAccounts") + boolean autoCreateAccounts(); + + @Key("AutoCreateAccountsAccessLevel") + int autoCreateAccountsAccessLevel(); + + @Key("DatapackRoot") + String getDatapackRoot(); + + @Key("Debug") + boolean isDebug(); + + @Key("LoginRestartSchedule") + boolean isLoginRestartEnabled(); + + @Key("LoginRestartTime") + int getLoginRestartTime(); +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/TelnetConfiguration.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/TelnetConfiguration.java new file mode 100644 index 00000000..6d16a438 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/config/TelnetConfiguration.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.config; + +import static java.util.concurrent.TimeUnit.MINUTES; +import static org.aeonbits.owner.Config.HotReloadType.ASYNC; +import static org.aeonbits.owner.Config.LoadType.MERGE; + +import java.util.List; + +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.HotReload; +import org.aeonbits.owner.Config.LoadPolicy; +import org.aeonbits.owner.Config.Sources; + +/** + * Telnet Configuration. + * @author Zoey76 + * @version 2.6.1.1 + */ +@Sources({ + "file:${L2J_HOME}/custom/login/config/telnet.properties", + "file:./config/telnet.properties", + "classpath:config/telnet.properties" +}) +@LoadPolicy(MERGE) +@HotReload(value = 20, unit = MINUTES, type = ASYNC) +public interface TelnetConfiguration extends Config { + + @Key("EnableTelnet") + boolean isEnabled(); + + @Key("Port") + int getPort(); + + @Key("Password") + String getPassword(); + + @Key("Hosts") + List getHosts(); +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/BaseMail.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/BaseMail.java new file mode 100644 index 00000000..ee8e4b53 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/BaseMail.java @@ -0,0 +1,135 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.mail; + +import static com.l2jserver.loginserver.config.Configuration.email; + +import java.io.UnsupportedEncodingException; +import java.util.Properties; + +import jakarta.mail.Authenticator; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.PasswordAuthentication; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.database.ConnectionFactory; + +/** + * Base Mail. + * @author mrTJO + * @version 2.6.1.0 + */ +public class BaseMail implements Runnable { + + private static final Logger LOG = LoggerFactory.getLogger(BaseMail.class); + + private MimeMessage _messageMime = null; + + private static class SmtpAuthenticator extends Authenticator { + private final PasswordAuthentication _auth; + + public SmtpAuthenticator() { + _auth = new PasswordAuthentication(email().getSmtpUsername(), email().getSmtpPassword()); + } + + @Override + public PasswordAuthentication getPasswordAuthentication() { + return _auth; + } + } + + public BaseMail(String account, String mailId, String... args) { + final var mailAddr = getUserMail(account); + if (mailAddr == null) { + return; + } + + final var content = MailSystem.getInstance().getMailContent(mailId); + if (content == null) { + return; + } + + final var message = compileHtml(account, content.getText(), args); + final var mailProp = new Properties(); + mailProp.put("mail.smtp.host", email().getHost()); + mailProp.put("mail.smtp.auth", email().isSmtpAuthRequired()); + mailProp.put("mail.smtp.port", email().getPort()); + mailProp.put("mail.smtp.socketFactory.port", email().getPort()); + mailProp.put("mail.smtp.socketFactory.class", email().getSmtpFactory()); + mailProp.put("mail.smtp.socketFactory.fallback", email().smtpFactoryCallback()); + final var authenticator = (email().isSmtpAuthRequired() ? new SmtpAuthenticator() : null); + final var mailSession = Session.getDefaultInstance(mailProp, authenticator); + + try { + _messageMime = new MimeMessage(mailSession); + _messageMime.setSubject(content.getSubject()); + try { + _messageMime.setFrom(new InternetAddress(email().getServerEmail(), email().getServerName())); + } catch (UnsupportedEncodingException ex) { + LOG.warn("Sender address {} is not Valid!", email().getServerEmail()); + } + _messageMime.setContent(message, "text/html"); + _messageMime.setRecipient(Message.RecipientType.TO, new InternetAddress(mailAddr)); + } catch (MessagingException ex) { + LOG.warn("There has been an error sending the email!", ex); + } + } + + private String compileHtml(String account, String html, String[] args) { + if (args != null) { + for (int i = 0; i < args.length; i++) { + html = html.replace("%var" + i + "%", args[i]); + } + } + return html.replace("%accountname%", account); + } + + private String getUserMail(String username) { + try (var con = ConnectionFactory.getInstance().getConnection(); + var statement = con.prepareStatement(email().getSelectQuery())) { + statement.setString(1, username); + try (var rs = statement.executeQuery()) { + if (rs.next()) { + return rs.getString(email().getDatabaseField()); + } + } + } catch (Exception ex) { + LOG.warn("Cannot select user mail!", ex); + } + return null; + } + + @Override + public void run() { + try { + if (_messageMime != null) { + Transport.send(_messageMime); + } + } catch (MessagingException ex) { + LOG.warn("There has been an error while sending email!", ex); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/MailSystem.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/MailSystem.java new file mode 100644 index 00000000..200e3980 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/mail/MailSystem.java @@ -0,0 +1,134 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.mail; + +import static com.l2jserver.loginserver.config.Configuration.email; +import static com.l2jserver.loginserver.config.Configuration.server; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +/** + * Mail System. + * @author mrTJO + * @version 2.6.1.0 + */ +public class MailSystem { + + private static final Logger LOG = LoggerFactory.getLogger(MailSystem.class); + + private final Map _mailData = new HashMap<>(); + + public MailSystem() { + loadMails(); + } + + public void sendMail(String account, String messageId, String... args) { + BaseMail mail = new BaseMail(account, messageId, args); + mail.run(); + } + + private void loadMails() { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setIgnoringComments(true); + File file = new File(server().getDatapackRoot() + "data/mail/MailList.xml"); + if (!file.exists()) { + LOG.warn("Cannot load email system - Missing file MailList.xml"); + return; + } + + Document doc; + try { + doc = factory.newDocumentBuilder().parse(file); + } catch (Exception ex) { + LOG.warn("Could not parse MailList.xml file!", ex); + return; + } + + Node n = doc.getFirstChild(); + for (Node d = n.getFirstChild(); d != null; d = d.getNextSibling()) { + if (d.getNodeName().equals("mail")) { + String mailId = d.getAttributes().getNamedItem("id").getNodeValue(); + String subject = d.getAttributes().getNamedItem("subject").getNodeValue(); + String maFile = d.getAttributes().getNamedItem("file").getNodeValue(); + + File mailFile = new File(server().getDatapackRoot() + "data/mail/" + maFile); + try (FileInputStream fis = new FileInputStream(mailFile); + BufferedInputStream bis = new BufferedInputStream(fis)) { + int bytes = bis.available(); + byte[] raw = new byte[bytes]; + + bis.read(raw); + String html = new String(raw, UTF_8); + html = html.replaceAll(System.lineSeparator(), "\n"); + html = html.replace("%servermail%", email().getServerEmail()); + html = html.replace("%servername%", email().getServerName()); + + _mailData.put(mailId, new MailContent(subject, html)); + } catch (IOException ex) { + LOG.warn("There has been an error while reading {}!", maFile, ex); + } + } + } + LOG.info("Email system loaded."); + } + + public static class MailContent { + private final String _subject; + private final String _text; + + public MailContent(String subject, String text) { + _subject = subject; + _text = text; + } + + public String getSubject() { + return _subject; + } + + public String getText() { + return _text; + } + } + + public MailContent getMailContent(String mailId) { + return _mailData.get(mailId); + } + + public static MailSystem getInstance() { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder { + protected static final MailSystem INSTANCE = new MailSystem(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/AccountInfo.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/AccountInfo.java new file mode 100644 index 00000000..a287fa71 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/AccountInfo.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.model; + +import java.util.Objects; + +/** + * Account Info. + * @author HorridoJoho + * @version 2.6.1.0 + */ +public final class AccountInfo { + private final String _login; + private final String _passHash; + private final int _accessLevel; + private final int _lastServer; + + public AccountInfo(final String login, final String passHash, final int accessLevel, final int lastServer) { + Objects.requireNonNull(login, "login parameter is null"); + Objects.requireNonNull(passHash, "passHash parameter is null"); + + if (login.isEmpty()) { + throw new IllegalArgumentException("login string is empty"); + } + if (passHash.isEmpty()) { + throw new IllegalArgumentException("passHash string is empty"); + } + + _login = login.toLowerCase(); + _passHash = passHash; + _accessLevel = accessLevel; + _lastServer = lastServer; + } + + public boolean checkPassHash(final String passHash) { + return _passHash.equals(passHash); + } + + public String getLogin() { + return _login; + } + + public int getAccessLevel() { + return _accessLevel; + } + + public int getLastServer() { + return _lastServer; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/GameServerVersion.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/GameServerVersion.java new file mode 100644 index 00000000..4fabfc42 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/model/GameServerVersion.java @@ -0,0 +1,107 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * Game Server version. + * @author Zoey76 + * @version 2.6.1.8 + */ +public enum GameServerVersion { + // Saga I: The Chaotic Chronicle + PRELUDE_CLOSED_BETA(-1, "Prelude Closed Beta"), + PRELUDE(0, "Prelude"), + HARBINGERS_OF_WAR(1, "Chronicle I: Harbingers of War"), + AGE_OF_SPLENDOR(2, "Chronicle II: Age of Splendor"), + RISE_OF_DARKNESS(3, "Chronicle III: Rise of Darkness"), + SCIONS_OF_DESTINY(4, "Chronicle IV: Scions of Destiny"), + OATH_OF_BLOOD(5, "Chronicle V: Oath of Blood"), + // Saga II: The Chaotic Throne + INTERLUDE(6, "Interlude"), + // Throne I + THE_KAMAEL(7, "The Kamael"), + HELLBOUND(8, "Hellbound"), + // Throne II + GRACIA_1(9, "Gracia"), + GRACIA_2(10, "Gracia Part 2"), + GRACIA_FINAL(11, "Gracia Final"), + EPILOGUE(12, "Gracia Epilogue"), + FREYA(13, "Freya"), + HIGH_FIVE(14, "High Five"), + // Saga III: Goddess of Destruction + // Chapter 1 + AWAKENING(15, "Awakening"), + HARMONY(16, "Harmony"), + // Chapter 2 + TAUTI(17, "Tauti"), + GLORY_DAYS(18, "Glory Days"), + // Chapter 3 + LIVINDOR(19, "Lindvior"), + // Saga IV: Epic Tale of Aden + // Episode 1 + ERTHEIA(20, "Ertheia"), + // Episode 2 + VALIANCE(21, "Valiance"), + // Episode 2.5 + INFINITE_ODYSSEY(22, "Infinite Odyssey"), + UNDERGROUND(23, "Underground"), + HELIOS(24, "Helios"), + GRAND_CRUSADE(25, "Grand Crusade"), + SALVATION(26, "Salvation"), + ETINAS_FATE(27, "Etina's Fate"), + FAFURION(28, "Fafurion"), + PREULDE_OF_WAR(29, "Prelude of War"), + DAWN_OF_HEROES(30, "Dawn of Heroes"); + + static final Map versions = new HashMap<>(); + + int id; + + String description; + + static { + for (var p : GameServerVersion.values()) { + versions.put(p.getId(), p.getDescription()); + } + } + + GameServerVersion(int id, String description) { + this.id = id; + this.description = description; + } + + public int getId() { + return id; + } + + public String getDescription() { + return description; + } + + public static Map versions() { + return versions; + } + + public static String valueOf(int id) { + return versions.get(id); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2JGameServerPacketHandler.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2JGameServerPacketHandler.java new file mode 100644 index 00000000..1daf5f18 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2JGameServerPacketHandler.java @@ -0,0 +1,121 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network; + +import static com.l2jserver.loginserver.config.Configuration.email; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; +import com.l2jserver.loginserver.network.gameserverpackets.BlowFishKey; +import com.l2jserver.loginserver.network.gameserverpackets.ChangeAccessLevel; +import com.l2jserver.loginserver.network.gameserverpackets.ChangePassword; +import com.l2jserver.loginserver.network.gameserverpackets.GameServerAuth; +import com.l2jserver.loginserver.network.gameserverpackets.PlayerAuthRequest; +import com.l2jserver.loginserver.network.gameserverpackets.PlayerInGame; +import com.l2jserver.loginserver.network.gameserverpackets.PlayerLogout; +import com.l2jserver.loginserver.network.gameserverpackets.PlayerTracert; +import com.l2jserver.loginserver.network.gameserverpackets.ReplyCharacters; +import com.l2jserver.loginserver.network.gameserverpackets.RequestSendMail; +import com.l2jserver.loginserver.network.gameserverpackets.RequestTempBan; +import com.l2jserver.loginserver.network.gameserverpackets.ServerStatus; +import com.l2jserver.loginserver.network.loginserverpackets.LoginServerFail; + +/** + * Game Server packet handler. + * @author mrTJO + * @version 2.6.1.0 + */ +public class L2JGameServerPacketHandler { + + private static final Logger LOG = LoggerFactory.getLogger(L2JGameServerPacketHandler.class); + + public enum GameServerState { + CONNECTED, + BF_CONNECTED, + AUTHED + } + + public static BaseRecievePacket handlePacket(byte[] data, GameServerThread server) { + BaseRecievePacket msg = null; + int opcode = data[0] & 0xff; + GameServerState state = server.getLoginConnectionState(); + switch (state) { + case CONNECTED: + if (opcode == 0x00) { + msg = new BlowFishKey(data, server); + } else { + LOG.warn("Unknown Opcode {} in state {} from game server, closing connection!", Integer.toHexString(opcode).toUpperCase(), state); + server.forceClose(LoginServerFail.NOT_AUTHED); + } + break; + case BF_CONNECTED: + if (opcode == 0x01) { + msg = new GameServerAuth(data, server); + } else { + LOG.warn("Unknown Opcode {} in state {} from game server, closing connection!", Integer.toHexString(opcode).toUpperCase(), state); + server.forceClose(LoginServerFail.NOT_AUTHED); + } + break; + case AUTHED: + switch (opcode) { + case 0x02: + msg = new PlayerInGame(data, server); + break; + case 0x03: + msg = new PlayerLogout(data, server); + break; + case 0x04: + msg = new ChangeAccessLevel(data, server); + break; + case 0x05: + msg = new PlayerAuthRequest(data, server); + break; + case 0x06: + msg = new ServerStatus(data, server); + break; + case 0x07: + msg = new PlayerTracert(data); + break; + case 0x08: + msg = new ReplyCharacters(data, server); + break; + case 0x09: + if (email().isEnabled()) { + msg = new RequestSendMail(data); + } + break; + case 0x0A: + msg = new RequestTempBan(data); + break; + case 0x0B: + new ChangePassword(data); + break; + default: + LOG.warn("Unknown Opcode {} in state {} from GameServer, closing connection!", Integer.toHexString(opcode).toUpperCase(), state); + server.forceClose(LoginServerFail.NOT_AUTHED); + break; + } + break; + } + return msg; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginClient.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginClient.java new file mode 100644 index 00000000..fbcbe0a2 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginClient.java @@ -0,0 +1,252 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network; + +import java.io.IOException; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.security.interfaces.RSAPrivateKey; +import java.util.HashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.util.Rnd; +import com.l2jserver.loginserver.LoginController; +import com.l2jserver.loginserver.SessionKey; +import com.l2jserver.loginserver.network.serverpackets.L2LoginServerPacket; +import com.l2jserver.loginserver.network.serverpackets.LoginFail; +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; +import com.l2jserver.loginserver.network.serverpackets.PlayFail; +import com.l2jserver.loginserver.network.serverpackets.PlayFail.PlayFailReason; +import com.l2jserver.loginserver.security.ScrambledKeyPair; +import com.l2jserver.loginserver.security.crypt.LoginCrypt; +import com.l2jserver.mmocore.MMOClient; +import com.l2jserver.mmocore.MMOConnection; +import com.l2jserver.mmocore.SendablePacket; + +/** + * Represents a client connected into the LoginServer + * @author KenM + * @version 2.6.1.0 + */ +public final class L2LoginClient extends MMOClient> { + + private static final Logger LOG = LoggerFactory.getLogger(L2LoginClient.class); + + public enum LoginClientState { + CONNECTED, + AUTHED_GG, + AUTHED_LOGIN + } + + private LoginClientState _state; + + private final LoginCrypt _loginCrypt; + + private final ScrambledKeyPair _scrambledPair; + + private final byte[] _blowfishKey; + + private String _account; + + private int _accessLevel; + + private int _lastServer; + + private SessionKey _sessionKey; + + private final int _sessionId; + + private boolean _joinedGS; + + private Map _charsOnServers; + + private Map _charsToDelete; + + private final long _connectionStartTime; + + public L2LoginClient(MMOConnection con) { + super(con); + _state = LoginClientState.CONNECTED; + _scrambledPair = LoginController.getInstance().getScrambledRSAKeyPair(); + _blowfishKey = LoginController.getInstance().getBlowfishKey(); + _sessionId = Rnd.nextInt(); + _connectionStartTime = System.currentTimeMillis(); + _loginCrypt = new LoginCrypt(); + _loginCrypt.setKey(_blowfishKey); + } + + @Override + public boolean decrypt(ByteBuffer buf, int size) { + try { + if (!_loginCrypt.decrypt(buf.array(), buf.position(), size)) { + LOG.warn("Wrong checksum from client {}!", this); + super.getConnection().close((SendablePacket) null); + return false; + } + return true; + } catch (IOException ex) { + LOG.warn("There has been an error decrypting message!", ex); + super.getConnection().close((SendablePacket) null); + return false; + } + } + + @Override + public boolean encrypt(ByteBuffer buf, int size) { + final int offset = buf.position(); + try { + size = _loginCrypt.encrypt(buf.array(), offset, size); + } catch (IOException ex) { + LOG.warn("There has been an error encrypting message!", ex); + return false; + } + buf.position(offset + size); + return true; + } + + public LoginClientState getState() { + return _state; + } + + public void setState(LoginClientState state) { + _state = state; + } + + public byte[] getBlowfishKey() { + return _blowfishKey; + } + + public byte[] getScrambledModulus() { + return _scrambledPair.getScrambledModulus(); + } + + public RSAPrivateKey getRSAPrivateKey() { + return (RSAPrivateKey) _scrambledPair.getPair().getPrivate(); + } + + public String getAccount() { + return _account; + } + + public void setAccount(String account) { + _account = account; + } + + public void setAccessLevel(int accessLevel) { + _accessLevel = accessLevel; + } + + public int getAccessLevel() { + return _accessLevel; + } + + public void setLastServer(int lastServer) { + _lastServer = lastServer; + } + + public int getLastServer() { + return _lastServer; + } + + public int getSessionId() { + return _sessionId; + } + + public boolean hasJoinedGS() { + return _joinedGS; + } + + public void setJoinedGS(boolean val) { + _joinedGS = val; + } + + public void setSessionKey(SessionKey sessionKey) { + _sessionKey = sessionKey; + } + + public SessionKey getSessionKey() { + return _sessionKey; + } + + public long getConnectionStartTime() { + return _connectionStartTime; + } + + public void sendPacket(L2LoginServerPacket lsp) { + getConnection().sendPacket(lsp); + } + + public void close(LoginFailReason reason) { + getConnection().close(new LoginFail(reason)); + } + + public void close(PlayFailReason reason) { + getConnection().close(new PlayFail(reason)); + } + + public void close(L2LoginServerPacket lsp) { + getConnection().close(lsp); + } + + public void setCharsOnServ(int servId, int chars) { + if (_charsOnServers == null) { + _charsOnServers = new HashMap<>(); + } + _charsOnServers.put(servId, chars); + } + + public Map getCharsOnServ() { + return _charsOnServers; + } + + public void serCharsWaitingDelOnServ(int servId, long[] charsToDel) { + if (_charsToDelete == null) { + _charsToDelete = new HashMap<>(); + } + _charsToDelete.put(servId, charsToDel); + } + + public Map getCharsWaitingDelOnServ() { + return _charsToDelete; + } + + @Override + public void onDisconnection() { + if (!hasJoinedGS() || ((getConnectionStartTime() + LoginController.LOGIN_TIMEOUT) < System.currentTimeMillis())) { + LoginController.getInstance().removeAuthedLoginClient(getAccount()); + } + } + + @Override + public String toString() { + InetAddress address = getConnection().getInetAddress(); + if (getState() == LoginClientState.AUTHED_LOGIN) { + return "[" + getAccount() + " (" + (address == null ? "disconnected" : address.getHostAddress()) + ")]"; + } + return "[" + (address == null ? "disconnected" : address.getHostAddress()) + "]"; + } + + @Override + protected void onForcedDisconnection() { + // Empty + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginPacketHandler.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginPacketHandler.java new file mode 100644 index 00000000..fd5e97d1 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/L2LoginPacketHandler.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network; + +import java.nio.ByteBuffer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.loginserver.network.L2LoginClient.LoginClientState; +import com.l2jserver.loginserver.network.clientpackets.AuthGameGuard; +import com.l2jserver.loginserver.network.clientpackets.RequestAuthLogin; +import com.l2jserver.loginserver.network.clientpackets.RequestServerList; +import com.l2jserver.loginserver.network.clientpackets.RequestServerLogin; +import com.l2jserver.mmocore.PacketHandler; +import com.l2jserver.mmocore.ReceivablePacket; + +/** + * Handler for packets received by Login Server. + * @author KenM + * @version 2.6.1.0 + */ +public final class L2LoginPacketHandler implements PacketHandler { + + private static final Logger LOG = LoggerFactory.getLogger(L2LoginPacketHandler.class); + + @Override + public ReceivablePacket handlePacket(ByteBuffer buf, L2LoginClient client) { + int opcode = buf.get() & 0xFF; + + ReceivablePacket packet = null; + LoginClientState state = client.getState(); + + switch (state) { + case CONNECTED: + if (opcode == 0x07) { + packet = new AuthGameGuard(); + } else { + debugOpcode(opcode, state); + } + break; + case AUTHED_GG: + if (opcode == 0x00) { + packet = new RequestAuthLogin(); + } else { + debugOpcode(opcode, state); + } + break; + case AUTHED_LOGIN: + switch (opcode) { + case 0x02 -> packet = new RequestServerLogin(); + case 0x05 -> packet = new RequestServerList(); + default -> debugOpcode(opcode, state); + } + break; + } + return packet; + } + + private void debugOpcode(int opcode, LoginClientState state) { + LOG.warn("Unknown Opcode {} for state {}!", opcode, state.name()); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/AuthGameGuard.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/AuthGameGuard.java new file mode 100644 index 00000000..ad4e6d0f --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/AuthGameGuard.java @@ -0,0 +1,79 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.clientpackets; + +import com.l2jserver.loginserver.network.L2LoginClient.LoginClientState; +import com.l2jserver.loginserver.network.serverpackets.GGAuth; +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; + +/** + * Format: ddddd + * @author -Wooden- + * @version 2.6.1.0 + */ +public class AuthGameGuard extends L2LoginClientPacket { + private int _sessionId; + private int _data1; + private int _data2; + private int _data3; + private int _data4; + + public int getSessionId() { + return _sessionId; + } + + public int getData1() { + return _data1; + } + + public int getData2() { + return _data2; + } + + public int getData3() { + return _data3; + } + + public int getData4() { + return _data4; + } + + @Override + protected boolean readImpl() { + if (super._buf.remaining() >= 20) { + _sessionId = readD(); + _data1 = readD(); + _data2 = readD(); + _data3 = readD(); + _data4 = readD(); + return true; + } + return false; + } + + @Override + public void run() { + if (_sessionId == getClient().getSessionId()) { + getClient().setState(LoginClientState.AUTHED_GG); + getClient().sendPacket(new GGAuth(getClient().getSessionId())); + } else { + getClient().close(LoginFailReason.REASON_ACCESS_FAILED); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/L2LoginClientPacket.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/L2LoginClientPacket.java new file mode 100644 index 00000000..ea46ed82 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/L2LoginClientPacket.java @@ -0,0 +1,47 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.clientpackets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.mmocore.ReceivablePacket; + +/** + * Login Client abstract packet. + * @author KenM + * @version 2.6.1.0 + */ +public abstract class L2LoginClientPacket extends ReceivablePacket { + + private static final Logger LOG = LoggerFactory.getLogger(L2LoginClientPacket.class); + + @Override + protected final boolean read() { + try { + return readImpl(); + } catch (Exception ex) { + LOG.error("Error reading {}!", getClass().getSimpleName(), ex); + return false; + } + } + + protected abstract boolean readImpl(); +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestAuthLogin.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestAuthLogin.java new file mode 100644 index 00000000..8c5ee797 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestAuthLogin.java @@ -0,0 +1,153 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.clientpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import java.net.InetAddress; + +import javax.crypto.Cipher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.LoginController; +import com.l2jserver.loginserver.LoginController.AuthLoginResult; +import com.l2jserver.loginserver.model.AccountInfo; +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.loginserver.network.L2LoginClient.LoginClientState; +import com.l2jserver.loginserver.network.serverpackets.AccountKicked; +import com.l2jserver.loginserver.network.serverpackets.AccountKicked.AccountKickedReason; +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; +import com.l2jserver.loginserver.network.serverpackets.LoginOk; +import com.l2jserver.loginserver.network.serverpackets.ServerList; + +/** + * Format: x 0 (a leading null) x: the rsa encrypted block with the login and password. + * @version 2.6.1.0 + */ +public class RequestAuthLogin extends L2LoginClientPacket { + + private static final Logger LOG = LoggerFactory.getLogger(RequestAuthLogin.class); + + private final byte[] _raw = new byte[128]; + + private String _user; + + private String _password; + + private int _ncotp; + + public String getPassword() { + return _password; + } + + public String getUser() { + return _user; + } + + public int getOneTimePassword() { + return _ncotp; + } + + @Override + public boolean readImpl() { + if (super._buf.remaining() >= 128) { + readB(_raw); + return true; + } + return false; + } + + @Override + public void run() { + byte[] decrypted; + final L2LoginClient client = getClient(); + try { + final Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding"); + rsaCipher.init(Cipher.DECRYPT_MODE, client.getRSAPrivateKey()); + decrypted = rsaCipher.doFinal(_raw, 0x00, 0x80); + } catch (Exception ex) { + LOG.warn("There has been an error trying to login!", ex); + return; + } + + try { + _user = new String(decrypted, 0x5E, 14).trim().toLowerCase(); + _password = new String(decrypted, 0x6C, 16).trim(); + _ncotp = decrypted[0x7c]; + _ncotp |= decrypted[0x7d] << 8; + _ncotp |= decrypted[0x7e] << 16; + _ncotp |= decrypted[0x7f] << 24; + } catch (Exception ex) { + LOG.warn("There has been an error parsing credentials!", ex); + return; + } + + InetAddress clientAddr = getClient().getConnection().getInetAddress(); + + final LoginController lc = LoginController.getInstance(); + AccountInfo info = lc.retrieveAccountInfo(clientAddr, _user, _password); + if (info == null) { + // user or pass wrong + client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG); + return; + } + + AuthLoginResult result = lc.tryCheckinAccount(client, clientAddr, info); + switch (result) { + case AUTH_SUCCESS -> { + client.setAccount(info.getLogin()); + client.setState(LoginClientState.AUTHED_LOGIN); + client.setSessionKey(lc.assignSessionKeyToClient(info.getLogin(), client)); + lc.getCharactersOnAccount(info.getLogin()); + if (server().showLicense()) { + client.sendPacket(new LoginOk(getClient().getSessionKey())); + } else { + getClient().sendPacket(new ServerList(getClient())); + } + } + case INVALID_PASSWORD -> client.close(LoginFailReason.REASON_USER_OR_PASS_WRONG); + case ACCOUNT_INACTIVE -> client.close(LoginFailReason.REASON_INACTIVE); + case ACCOUNT_BANNED -> client.close(new AccountKicked(AccountKickedReason.REASON_PERMANENTLY_BANNED)); + case ALREADY_ON_LS -> { + L2LoginClient oldClient = lc.getAuthedClient(info.getLogin()); + if (oldClient != null) { + // kick the other client + oldClient.close(LoginFailReason.REASON_ACCOUNT_IN_USE); + lc.removeAuthedLoginClient(info.getLogin()); + } + // kick also current client + client.close(LoginFailReason.REASON_ACCOUNT_IN_USE); + } + case ALREADY_ON_GS -> { + GameServerInfo gsi = lc.getAccountOnGameServer(info.getLogin()); + if (gsi != null) { + client.close(LoginFailReason.REASON_ACCOUNT_IN_USE); + + // kick from there + if (gsi.isAuthed()) { + gsi.getGameServerThread().kickPlayer(info.getLogin()); + } + } + } + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerList.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerList.java new file mode 100644 index 00000000..375e43f7 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerList.java @@ -0,0 +1,69 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.clientpackets; + +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; +import com.l2jserver.loginserver.network.serverpackets.ServerList; + +/** + *
+ * Format: ddc
+ * d: fist part of session id
+ * d: second part of session id
+ * c: ?
+ * 
+ * + * @version 2.6.1.0 + */ +public class RequestServerList extends L2LoginClientPacket { + private int _skey1; + private int _skey2; + private int _data3; + + public int getSessionKey1() { + return _skey1; + } + + public int getSessionKey2() { + return _skey2; + } + + public int getData3() { + return _data3; + } + + @Override + public boolean readImpl() { + if (super._buf.remaining() >= 8) { + _skey1 = readD(); // loginOk 1 + _skey2 = readD(); // loginOk 2 + return true; + } + return false; + } + + @Override + public void run() { + if (getClient().getSessionKey().checkLoginPair(_skey1, _skey2)) { + getClient().sendPacket(new ServerList(getClient())); + } else { + getClient().close(LoginFailReason.REASON_ACCESS_FAILED); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerLogin.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerLogin.java new file mode 100644 index 00000000..c590fa9a --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/clientpackets/RequestServerLogin.java @@ -0,0 +1,82 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.clientpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import com.l2jserver.loginserver.LoginController; +import com.l2jserver.loginserver.SessionKey; +import com.l2jserver.loginserver.network.serverpackets.LoginFail.LoginFailReason; +import com.l2jserver.loginserver.network.serverpackets.PlayFail.PlayFailReason; +import com.l2jserver.loginserver.network.serverpackets.PlayOk; + +/** + *
+ * Format is ddc
+ * d: first part of session id
+ * d: second part of session id
+ * c: server ID
+ * 
+ * + * @version 2.6.1.0 + */ +public class RequestServerLogin extends L2LoginClientPacket { + private int _skey1; + private int _skey2; + private int _serverId; + + public int getSessionKey1() { + return _skey1; + } + + public int getSessionKey2() { + return _skey2; + } + + public int getServerID() { + return _serverId; + } + + @Override + public boolean readImpl() { + if (super._buf.remaining() >= 9) { + _skey1 = readD(); + _skey2 = readD(); + _serverId = readC(); + return true; + } + return false; + } + + @Override + public void run() { + SessionKey sk = getClient().getSessionKey(); + // if we didn't show the license we can't check these values + if (!server().showLicense() || sk.checkLoginPair(_skey1, _skey2)) { + if (LoginController.getInstance().isLoginPossible(getClient(), _serverId)) { + getClient().setJoinedGS(true); + getClient().sendPacket(new PlayOk(sk)); + } else { + getClient().close(PlayFailReason.REASON_SERVER_OVERLOADED); + } + } else { + getClient().close(LoginFailReason.REASON_ACCESS_FAILED); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/BlowFishKey.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/BlowFishKey.java new file mode 100644 index 00000000..96686318 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/BlowFishKey.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; +import static com.l2jserver.loginserver.network.L2JGameServerPacketHandler.GameServerState.BF_CONNECTED; +import static javax.crypto.Cipher.DECRYPT_MODE; + +import javax.crypto.Cipher; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.commons.security.crypt.NewCrypt; +import com.l2jserver.loginserver.GameServerThread; + +/** + * Blowfish Key. + * @author -Wooden- + * @version 2.6.1.0 + */ +public class BlowFishKey extends BaseRecievePacket { + + private static final Logger LOG = LoggerFactory.getLogger(BlowFishKey.class); + + public BlowFishKey(byte[] decrypt, GameServerThread server) { + super(decrypt); + int size = readD(); + byte[] tempKey = readB(size); + try { + byte[] tempDecryptKey; + Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding"); + rsaCipher.init(DECRYPT_MODE, server.getPrivateKey()); + tempDecryptKey = rsaCipher.doFinal(tempKey); + // there are nulls before the key we must remove them + int i = 0; + int len = tempDecryptKey.length; + for (; i < len; i++) { + if (tempDecryptKey[i] != 0) { + break; + } + } + byte[] key = new byte[len - i]; + System.arraycopy(tempDecryptKey, i, key, 0, len - i); + + server.SetBlowFish(new NewCrypt(key)); + if (server().isDebug()) { + LOG.info("New BlowFish key received, Blowfish Engine initialized:"); + } + server.setLoginConnectionState(BF_CONNECTED); + } catch (Exception ex) { + LOG.error("There has been an error while decrypting blowfish key (RSA)!", ex); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangeAccessLevel.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangeAccessLevel.java new file mode 100644 index 00000000..b29db21d --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangeAccessLevel.java @@ -0,0 +1,45 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; +import com.l2jserver.loginserver.LoginController; + +/** + * Change Access Level packet. + * @author -Wooden- + * @version 2.6.1.0 + */ +public class ChangeAccessLevel extends BaseRecievePacket { + + private static final Logger LOG = LoggerFactory.getLogger(ChangeAccessLevel.class); + + public ChangeAccessLevel(byte[] decrypt, GameServerThread server) { + super(decrypt); + int level = readD(); + String account = readS(); + + LoginController.getInstance().setAccountAccessLevel(account, level); + LOG.info("Changed {} access level to {}.", account, level); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangePassword.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangePassword.java new file mode 100644 index 00000000..382973ed --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ChangePassword.java @@ -0,0 +1,109 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.security.MessageDigest; +import java.sql.ResultSet; +import java.util.Base64; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.database.ConnectionFactory; +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerTable; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.GameServerThread; + +/** + * Change Password packet. + * @author Nik + * @version 2.6.1.0 + */ +public class ChangePassword extends BaseRecievePacket { + + private static final Logger LOG = LoggerFactory.getLogger(ChangePassword.class); + + private static GameServerThread gst = null; + + public ChangePassword(byte[] decrypt) { + super(decrypt); + + String accountName = readS(); + String characterName = readS(); + String currentPassword = readS(); + String newPassword = readS(); + + for (GameServerInfo gsi : GameServerTable.getInstance().getRegisteredGameServers().values()) { + if ((gsi.getGameServerThread() != null) && gsi.getGameServerThread().hasAccountOnGameServer(accountName)) { + gst = gsi.getGameServerThread(); + } + } + + if (gst == null) { + return; + } + + if ((currentPassword == null) || (newPassword == null)) { + gst.ChangePasswordResponse((byte) 0, characterName, "Invalid password data! Try again."); + } else { + try { + final var md = MessageDigest.getInstance("SHA"); + final var raw = md.digest(currentPassword.getBytes(UTF_8)); + String currentPasswordEnc = Base64.getEncoder().encodeToString(raw); + String pass = null; + int passUpdated; + + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement("SELECT password FROM accounts WHERE login=?")) { + ps.setString(1, accountName); + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + pass = rs.getString("password"); + } + } + } + + if (currentPasswordEnc.equals(pass)) { + final var password = md.digest(newPassword.getBytes(UTF_8)); + final var newPasswordEnc = Base64.getEncoder().encodeToString(password); + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement("UPDATE accounts SET password=? WHERE login=?")) { + ps.setString(1, newPasswordEnc); + ps.setString(2, accountName); + passUpdated = ps.executeUpdate(); + } + + LOG.info("The password for account {} has been changed from {} to {}.", accountName, currentPasswordEnc, newPasswordEnc); + if (passUpdated > 0) { + gst.ChangePasswordResponse((byte) 1, characterName, "You have successfully changed your password!"); + } else { + gst.ChangePasswordResponse((byte) 0, characterName, "The password change was unsuccessful!"); + } + } else { + gst.ChangePasswordResponse((byte) 0, characterName, "The typed current password doesn't match with your current one."); + } + } catch (Exception ex) { + LOG.warn("Error while changing password for account {} requested by player {}!", accountName, characterName, ex); + } + } + } +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/GameServerAuth.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/GameServerAuth.java new file mode 100644 index 00000000..af9ccd40 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/GameServerAuth.java @@ -0,0 +1,173 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerTable; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.GameServerThread; +import com.l2jserver.loginserver.model.GameServerVersion; +import com.l2jserver.loginserver.network.L2JGameServerPacketHandler.GameServerState; +import com.l2jserver.loginserver.network.loginserverpackets.AuthResponse; +import com.l2jserver.loginserver.network.loginserverpackets.LoginServerFail; + +/** + * Game Server Auth packet. + * + *
+ * Format: CCCCHDDBD[S]
+ * C server version
+ * C desired ID
+ * C accept alternative ID
+ * C reserve Host
+ * H port
+ * D max players
+ * D hex Id size
+ * B hex Id
+ * D subnet size
+ * [S] subnets
+ * 
+ * + * @author -Wooden- + * @author Zoey76 + * @version 2.6.1.0 + */ +public class GameServerAuth extends BaseRecievePacket { + + private static final Logger LOG = LoggerFactory.getLogger(GameServerAuth.class); + + private final GameServerThread _server; + + private final byte[] _hexId; + + private final int _desiredId; + + @SuppressWarnings("unused") + private final boolean _hostReserved; + + private final boolean _acceptAlternativeId; + + private final int _maxPlayers; + + private final int _serverVersion; + + private final int _port; + + private final String[] _hosts; + + public GameServerAuth(byte[] decrypt, GameServerThread server) { + super(decrypt); + _server = server; + _serverVersion = readC(); + _desiredId = readC(); + _acceptAlternativeId = (readC() != 0); + _hostReserved = (readC() != 0); + _port = readH(); + _maxPlayers = readD(); + int size = readD(); + _hexId = readB(size); + size = 2 * readD(); + _hosts = new String[size]; + for (int i = 0; i < size; i++) { + _hosts[i] = readS(); + } + + if (server().isDebug()) { + LOG.info("Auth request received."); + } + + if (handleRegProcess()) { + server.sendPacket(new AuthResponse(server.getGameServerInfo().getId())); + LOG.info("Game Server {} enabled.", GameServerVersion.valueOf(_serverVersion)); + + server.broadcastToTelnet("GameServer [" + server.getServerId() + "] " + ServerNameDAO.getServer(server.getServerId()) + " is connected"); + server.setLoginConnectionState(GameServerState.AUTHED); + } + } + + private boolean handleRegProcess() { + if (!server().getServerVersions().contains(_serverVersion)) { + _server.forceClose(LoginServerFail.REASON_INVALID_GAME_SERVER_VERSION); + return false; + } + + GameServerTable gameServerTable = GameServerTable.getInstance(); + + int id = _desiredId; + byte[] hexId = _hexId; + + GameServerInfo gsi = gameServerTable.getRegisteredGameServerById(id); + // is there a game server registered with this id? + if (gsi != null) { + // does the hex id match? + if (Arrays.equals(gsi.getHexId(), hexId)) { + // check to see if this GS is already connected + synchronized (gsi) { + if (gsi.isAuthed()) { + _server.forceClose(LoginServerFail.REASON_ALREADY_LOGGED_IN); + return false; + } + _server.attachGameServerInfo(gsi, _port, _hosts, _maxPlayers); + } + } else { + // there is already a server registered with the desired id and different hex id + // try to register this one with an alternative id + if (server().isAcceptNetGameServer() && _acceptAlternativeId) { + gsi = new GameServerInfo(id, hexId, _server); + if (gameServerTable.registerWithFirstAvailableId(gsi)) { + _server.attachGameServerInfo(gsi, _port, _hosts, _maxPlayers); + gameServerTable.registerServerOnDB(gsi); + } else { + _server.forceClose(LoginServerFail.REASON_NO_FREE_ID); + return false; + } + } else { + // server id is already taken, and we cant get a new one for you + _server.forceClose(LoginServerFail.REASON_WRONG_HEXID); + return false; + } + } + } else { + // can we register on this id? + if (server().isAcceptNetGameServer()) { + gsi = new GameServerInfo(id, hexId, _server); + if (gameServerTable.register(id, gsi)) { + _server.attachGameServerInfo(gsi, _port, _hosts, _maxPlayers); + gameServerTable.registerServerOnDB(gsi); + } else { + // someone took this ID meanwhile + _server.forceClose(LoginServerFail.REASON_ID_RESERVED); + return false; + } + } else { + _server.forceClose(LoginServerFail.REASON_WRONG_HEXID); + return false; + } + } + return true; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerAuthRequest.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerAuthRequest.java new file mode 100644 index 00000000..88e592c5 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerAuthRequest.java @@ -0,0 +1,53 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; +import com.l2jserver.loginserver.LoginController; +import com.l2jserver.loginserver.SessionKey; +import com.l2jserver.loginserver.network.loginserverpackets.PlayerAuthResponse; + +/** + * Player Auth Request packet. + * @author -Wooden- + * @version 2.6.1.0 + */ +public class PlayerAuthRequest extends BaseRecievePacket { + + public PlayerAuthRequest(byte[] decrypt, GameServerThread server) { + super(decrypt); + String account = readS(); + int playKey1 = readD(); + int playKey2 = readD(); + int loginKey1 = readD(); + int loginKey2 = readD(); + SessionKey sessionKey = new SessionKey(loginKey1, loginKey2, playKey1, playKey2); + + PlayerAuthResponse authResponse; + SessionKey key = LoginController.getInstance().getKeyForAccount(account); + if ((key != null) && key.equals(sessionKey)) { + LoginController.getInstance().removeAuthedLoginClient(account); + authResponse = new PlayerAuthResponse(account, true); + } else { + authResponse = new PlayerAuthResponse(account, false); + } + server.sendPacket(authResponse); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerInGame.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerInGame.java new file mode 100644 index 00000000..edac78e2 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerInGame.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; + +/** + * Player In Game packet. + * @author -Wooden- + * @author Zoey76 + * @version 2.6.1.0 + */ +public class PlayerInGame extends BaseRecievePacket { + private static final Logger LOG = LoggerFactory.getLogger(PlayerInGame.class); + + public PlayerInGame(byte[] decrypt, GameServerThread server) { + super(decrypt); + int size = readH(); + for (int i = 0; i < size; i++) { + String account = readS(); + server.addAccountOnGameServer(account); + if (server().isDebug()) { + LOG.info("Account {} logged in Game Server {}[{}].", account, ServerNameDAO.getServer(server.getServerId()), server.getServerId()); + } + server.broadcastToTelnet("Account " + account + " logged in GameServer " + server.getServerId()); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerLogout.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerLogout.java new file mode 100644 index 00000000..65f33925 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerLogout.java @@ -0,0 +1,50 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import static com.l2jserver.loginserver.config.Configuration.server; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; + +/** + * Player Logout packet. + * @author -Wooden- + * @author Zoey76 + * @version 2.6.1.0 + */ +public class PlayerLogout extends BaseRecievePacket { + private static final Logger LOG = LoggerFactory.getLogger(PlayerLogout.class); + + public PlayerLogout(byte[] decrypt, GameServerThread server) { + super(decrypt); + String account = readS(); + + server.removeAccountOnGameServer(account); + if (server().isDebug()) { + LOG.info("Player {} logged out from game server {}[{}].", account, ServerNameDAO.getServer(server.getServerId()), server.getServerId()); + } + + server.broadcastToTelnet("Player " + account + " disconnected from Game Server " + server.getServerId() + "."); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerTracert.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerTracert.java new file mode 100644 index 00000000..eb13cd8b --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/PlayerTracert.java @@ -0,0 +1,42 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.LoginController; + +/** + * Player Tracert packet. + * @author mrTJO + * @version 2.6.1.0 + */ +public class PlayerTracert extends BaseRecievePacket { + + public PlayerTracert(byte[] decrypt) { + super(decrypt); + String account = readS(); + String pcIp = readS(); + String hop1 = readS(); + String hop2 = readS(); + String hop3 = readS(); + String hop4 = readS(); + + LoginController.getInstance().setAccountLastTracert(account, pcIp, hop1, hop2, hop3, hop4); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ReplyCharacters.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ReplyCharacters.java new file mode 100644 index 00000000..062b5ce8 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ReplyCharacters.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerThread; +import com.l2jserver.loginserver.LoginController; + +/** + * @author mrTJO + * @version 2.6.1.0 + */ +public class ReplyCharacters extends BaseRecievePacket { + public ReplyCharacters(byte[] decrypt, GameServerThread server) { + super(decrypt); + String account = readS(); + int chars = readC(); + int charsToDel = readC(); + long[] charsList = new long[charsToDel]; + for (int i = 0; i < charsToDel; i++) { + charsList[i] = readQ(); + } + LoginController.getInstance().setCharactersOnServer(account, chars, charsList, server.getServerId()); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestSendMail.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestSendMail.java new file mode 100644 index 00000000..0e2089bf --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestSendMail.java @@ -0,0 +1,41 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.mail.MailSystem; + +/** + * @author mrTJO + * @version 2.6.1.0 + */ +public class RequestSendMail extends BaseRecievePacket { + + public RequestSendMail(byte[] decrypt) { + super(decrypt); + String accountName = readS(); + String mailId = readS(); + int argNum = readC(); + String[] args = new String[argNum]; + for (int i = 0; i < argNum; i++) { + args[i] = readS(); + } + MailSystem.getInstance().sendMail(accountName, mailId, args); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestTempBan.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestTempBan.java new file mode 100644 index 00000000..012f4b88 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/RequestTempBan.java @@ -0,0 +1,76 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.database.ConnectionFactory; +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.LoginController; + +/** + * Request Temp Ban packet. + * @author mrTJO + * @version 2.6.1.0 + */ +public class RequestTempBan extends BaseRecievePacket { + + private static final Logger LOG = LoggerFactory.getLogger(RequestTempBan.class); + + private final String _accountName; + + @SuppressWarnings("unused") + private String _banReason; + + private final String _ip; + + private final long _banTime; + + public RequestTempBan(byte[] decrypt) { + super(decrypt); + _accountName = readS(); + _ip = readS(); + _banTime = readQ(); + boolean haveReason = readC() != 0; + if (haveReason) { + _banReason = readS(); + } + banUser(); + } + + private void banUser() { + try (var con = ConnectionFactory.getInstance().getConnection(); + var ps = con.prepareStatement("INSERT INTO account_data VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value=?")) { + ps.setString(1, _accountName); + ps.setString(2, "ban_temp"); + ps.setString(3, Long.toString(_banTime)); + ps.setString(4, Long.toString(_banTime)); + ps.execute(); + } catch (Exception ex) { + LOG.warn("There has been an error inserting ban for account {}!", _accountName, ex); + } + + try { + LoginController.getInstance().addBanForAddress(_ip, _banTime); + } catch (Exception e) { + + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ServerStatus.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ServerStatus.java new file mode 100644 index 00000000..c6050f3f --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/gameserverpackets/ServerStatus.java @@ -0,0 +1,93 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.gameserverpackets; + +import com.l2jserver.commons.network.BaseRecievePacket; +import com.l2jserver.loginserver.GameServerTable; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.GameServerThread; + +/** + * Server Status packet. + * @author -Wooden- + * @version 2.6.1.0 + */ +public class ServerStatus extends BaseRecievePacket { + + public static final String[] STATUS_STRING = { + "Auto", + "Good", + "Normal", + "Full", + "Down", + "Gm Only" + }; + + public static final int SERVER_LIST_STATUS = 0x01; + public static final int SERVER_TYPE = 0x02; + public static final int SERVER_LIST_SQUARE_BRACKET = 0x03; + public static final int MAX_PLAYERS = 0x04; + public static final int TEST_SERVER = 0x05; + public static final int SERVER_AGE = 0x06; + + // Server Status + public static final int STATUS_AUTO = 0x00; + public static final int STATUS_GOOD = 0x01; + public static final int STATUS_NORMAL = 0x02; + public static final int STATUS_FULL = 0x03; + public static final int STATUS_DOWN = 0x04; + public static final int STATUS_GM_ONLY = 0x05; + + // Server Types + public static final int SERVER_NORMAL = 0x01; + public static final int SERVER_RELAX = 0x02; + public static final int SERVER_TEST = 0x04; + public static final int SERVER_NOLABEL = 0x08; + public static final int SERVER_CREATION_RESTRICTED = 0x10; + public static final int SERVER_EVENT = 0x20; + public static final int SERVER_FREE = 0x40; + + // Server Ages + public static final int SERVER_AGE_ALL = 0x00; + public static final int SERVER_AGE_15 = 0x0F; + public static final int SERVER_AGE_18 = 0x12; + + public static final int ON = 0x01; + public static final int OFF = 0x00; + + public ServerStatus(byte[] decrypt, GameServerThread server) { + super(decrypt); + + GameServerInfo gsi = GameServerTable.getInstance().getRegisteredGameServerById(server.getServerId()); + if (gsi != null) { + int size = readD(); + for (int i = 0; i < size; i++) { + int type = readD(); + int value = readD(); + switch (type) { + case SERVER_LIST_STATUS -> gsi.setStatus(value); + case SERVER_LIST_SQUARE_BRACKET -> gsi.setShowingBrackets(value == ON); + case MAX_PLAYERS -> gsi.setMaxPlayers(value); + case SERVER_TYPE -> gsi.setServerType(value); + case SERVER_AGE -> gsi.setAgeLimit(value); + } + } + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/AuthResponse.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/AuthResponse.java new file mode 100644 index 00000000..0c1704a0 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/AuthResponse.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.dao.ServerNameDAO; +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author -Wooden- + * @version 2.6.1.0 + */ +public class AuthResponse extends BaseSendablePacket { + + public AuthResponse(int serverId) { + writeC(0x02); + writeC(serverId); + writeS(ServerNameDAO.getServer(serverId)); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/ChangePasswordResponse.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/ChangePasswordResponse.java new file mode 100644 index 00000000..2e7ad92b --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/ChangePasswordResponse.java @@ -0,0 +1,39 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author Nik + * @version 2.6.1.0 + */ +public class ChangePasswordResponse extends BaseSendablePacket { + public ChangePasswordResponse(byte successful, String characterName, String msgToSend) { + writeC(0x06); + // writeC(successful); //0 false, 1 true + writeS(characterName); + writeS(msgToSend); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} \ No newline at end of file diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/InitLS.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/InitLS.java new file mode 100644 index 00000000..e8560fd8 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/InitLS.java @@ -0,0 +1,48 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * Initialize Login Server packet. + * + *
+ * C ID 0x00
+ * D key size
+ * B key
+ * 
+ * + * @author -Wooden- + * @author Zoey76 + * @version 2.6.1.0 + */ +public class InitLS extends BaseSendablePacket { + + public InitLS(byte[] publicKey) { + writeC(0x00); + writeD(publicKey.length); + writeB(publicKey); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/KickPlayer.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/KickPlayer.java new file mode 100644 index 00000000..4b0f4c72 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/KickPlayer.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author -Wooden- + * @version 2.6.1.0 + */ +public class KickPlayer extends BaseSendablePacket { + public KickPlayer(String account) { + writeC(0x04); + writeS(account); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/LoginServerFail.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/LoginServerFail.java new file mode 100644 index 00000000..d29aa2c8 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/LoginServerFail.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author -Wooden- + * @version 2.6.1.0 + */ +public class LoginServerFail extends BaseSendablePacket { + + public static final int REASON_INVALID_GAME_SERVER_VERSION = 0; + + public static final int REASON_IP_BANNED = 1; + + public static final int REASON_IP_RESERVED = 2; + + public static final int REASON_WRONG_HEXID = 3; + + public static final int REASON_ID_RESERVED = 4; + + public static final int REASON_NO_FREE_ID = 5; + + public static final int NOT_AUTHED = 6; + + public static final int REASON_ALREADY_LOGGED_IN = 7; + + public LoginServerFail(int reason) { + writeC(0x01); + writeC(reason); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/PlayerAuthResponse.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/PlayerAuthResponse.java new file mode 100644 index 00000000..448ec8ab --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/PlayerAuthResponse.java @@ -0,0 +1,38 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author -Wooden- + * @version 2.6.1.0 + */ +public class PlayerAuthResponse extends BaseSendablePacket { + public PlayerAuthResponse(String account, boolean response) { + writeC(0x03); + writeS(account); + writeC(response ? 1 : 0); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/RequestCharacters.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/RequestCharacters.java new file mode 100644 index 00000000..ab1428d9 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/loginserverpackets/RequestCharacters.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.loginserverpackets; + +import com.l2jserver.commons.network.BaseSendablePacket; + +/** + * @author mrTJO + * @version 2.6.1.0 + */ +public class RequestCharacters extends BaseSendablePacket { + public RequestCharacters(String account) { + writeC(0x05); + writeS(account); + } + + @Override + public byte[] getContent() { + return getBytes(); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/AccountKicked.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/AccountKicked.java new file mode 100644 index 00000000..8b2d8406 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/AccountKicked.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +/** + * @author KenM + * @version 2.6.1.0 + */ +public final class AccountKicked extends L2LoginServerPacket { + public enum AccountKickedReason { + REASON_DATA_STEALER(0x01), + REASON_GENERIC_VIOLATION(0x08), + REASON_7_DAYS_SUSPENDED(0x10), + REASON_PERMANENTLY_BANNED(0x20); + + private final int _code; + + AccountKickedReason(int code) { + _code = code; + } + + public final int getCode() { + return _code; + } + } + + private final AccountKickedReason _reason; + + public AccountKicked(AccountKickedReason reason) { + _reason = reason; + } + + @Override + protected void write() { + writeC(0x02); + writeD(_reason.getCode()); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/GGAuth.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/GGAuth.java new file mode 100644 index 00000000..d0bea4d4 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/GGAuth.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +/** + * Format: d d: response + * @version 2.6.1.0 + */ +public final class GGAuth extends L2LoginServerPacket { + + public static final int SKIP_GG_AUTH_REQUEST = 0x0b; + + private final int _response; + + public GGAuth(int response) { + _response = response; + } + + @Override + protected void write() { + writeC(0x0b); + writeD(_response); + writeD(0x00); + writeD(0x00); + writeD(0x00); + writeD(0x00); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/Init.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/Init.java new file mode 100644 index 00000000..ed0ec68d --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/Init.java @@ -0,0 +1,73 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +import com.l2jserver.loginserver.network.L2LoginClient; + +/** + *
+ * Format: dd b dddd s
+ * d: session id
+ * d: protocol revision
+ * b: 0x90 bytes : 0x80 bytes for the scrambled RSA public key
+ *                 0x10 bytes at 0x00
+ * d: unknown
+ * d: unknown
+ * d: unknown
+ * d: unknown
+ * s: blowfish key
+ * 
+ * + * @version 2.6.1.0 + */ +public final class Init extends L2LoginServerPacket { + private final int _sessionId; + + private final byte[] _publicKey; + private final byte[] _blowfishKey; + + public Init(L2LoginClient client) { + this(client.getScrambledModulus(), client.getBlowfishKey(), client.getSessionId()); + } + + public Init(byte[] publicKey, byte[] blowfishKey, int sessionId) { + _sessionId = sessionId; + _publicKey = publicKey; + _blowfishKey = blowfishKey; + } + + @Override + protected void write() { + writeC(0x00); // init packet id + + writeD(_sessionId); // session id + writeD(0x0000c621); // protocol revision + + writeB(_publicKey); // RSA Public Key + + // unk GG related? + writeD(0x29DD954E); + writeD(0x77C39CFC); + writeD(0x97ADB620); + writeD(0x07BDE0F7); + + writeB(_blowfishKey); // BlowFish key + writeC(0x00); // null termination ;) + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/L2LoginServerPacket.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/L2LoginServerPacket.java new file mode 100644 index 00000000..8d9ef386 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/L2LoginServerPacket.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.mmocore.SendablePacket; + +/** + * @author KenM + * @version 2.6.1.0 + */ +public abstract class L2LoginServerPacket extends SendablePacket { + +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginFail.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginFail.java new file mode 100644 index 00000000..de750133 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginFail.java @@ -0,0 +1,89 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +/** + * Format: d d: the failure reason + * @version 2.6.1.0 + */ +public final class LoginFail extends L2LoginServerPacket { + public enum LoginFailReason { + REASON_NO_MESSAGE(0x00), + REASON_SYSTEM_ERROR_LOGIN_LATER(0x01), + REASON_USER_OR_PASS_WRONG(0x02), + REASON_ACCESS_FAILED_TRY_AGAIN_LATER(0x04), + REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT(0x05), + REASON_ACCOUNT_IN_USE(0x07), + REASON_UNDER_18_YEARS_KR(0x0C), + REASON_SERVER_OVERLOADED(0x0F), + REASON_SERVER_MAINTENANCE(0x10), + REASON_TEMP_PASS_EXPIRED(0x11), + REASON_GAME_TIME_EXPIRED(0x12), + REASON_NO_TIME_LEFT(0x13), + REASON_SYSTEM_ERROR(0x14), + REASON_ACCESS_FAILED(0x15), + REASON_RESTRICTED_IP(0x16), + REASON_WEEK_USAGE_FINISHED(0x1E), + REASON_SECURITY_CARD_NUMBER_INVALID(0x1F), + REASON_AGE_NOT_VERIFIED_CANT_LOG_BETWEEN_10PM_6AM(0x20), + REASON_SERVER_CANNOT_BE_ACCESSED_BY_YOUR_COUPON(0x21), + REASON_DUAL_BOX(0x23), + REASON_INACTIVE(0x24), + REASON_USER_AGREEMENT_REJECTED_ON_WEBSITE(0x25), + REASON_GUARDIAN_CONSENT_REQUIRED(0x26), + REASON_USER_AGREEMENT_DECLINED_OR_WITHDRAWAL_REQUEST(0x27), + REASON_ACCOUNT_SUSPENDED_CALL(0x28), + REASON_CHANGE_PASSWORD_AND_QUIZ_ON_WEBSITE(0x29), + REASON_ALREADY_LOGGED_INTO_10_ACCOUNTS(0x2A), + REASON_MASTER_ACCOUNT_RESTRICTED(0x2B), + REASON_CERTIFICATION_FAILED(0x2E), + REASON_TELEPHONE_CERTIFICATION_UNAVAILABLE(0x2F), + REASON_TELEPHONE_SIGNALS_DELAYED(0x30), + REASON_CERTIFICATION_FAILED_LINE_BUSY(0x31), + REASON_CERTIFICATION_SERVICE_NUMBER_EXPIRED_OR_INCORRECT(0x32), + REASON_CERTIFICATION_SERVICE_CURRENTLY_BEING_CHECKED(0x33), + REASON_CERTIFICATION_SERVICE_CANT_BE_USED_HEAVY_VOLUME(0x34), + REASON_CERTIFICATION_SERVICE_EXPIRED_GAMEPLAY_BLOCKED(0x35), + REASON_CERTIFICATION_FAILED_3_TIMES_GAMEPLAY_BLOCKED_30_MIN(0x36), + REASON_CERTIFICATION_DAILY_USE_EXCEEDED(0x37), + REASON_CERTIFICATION_UNDERWAY_TRY_AGAIN_LATER(0x38); + + private final int _code; + + LoginFailReason(int code) { + _code = code; + } + + public final int getCode() { + return _code; + } + } + + private final LoginFailReason _reason; + + public LoginFail(LoginFailReason reason) { + _reason = reason; + } + + @Override + protected void write() { + writeC(0x01); + writeC(_reason.getCode()); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginOk.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginOk.java new file mode 100644 index 00000000..dc56760f --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/LoginOk.java @@ -0,0 +1,59 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +import com.l2jserver.loginserver.SessionKey; + +/** + *
+ * Format: dddddddd
+ * f: the session key
+ * d: ?
+ * d: ?
+ * d: ?
+ * d: ?
+ * d: ?
+ * d: ?
+ * b: 16 bytes - unknown
+ * 
+ * + * @version 2.6.1.0 + */ +public final class LoginOk extends L2LoginServerPacket { + private final int _loginOk1, _loginOk2; + + public LoginOk(SessionKey sessionKey) { + _loginOk1 = sessionKey.loginOkID1; + _loginOk2 = sessionKey.loginOkID2; + } + + @Override + protected void write() { + writeC(0x03); + writeD(_loginOk1); + writeD(_loginOk2); + writeD(0x00); + writeD(0x00); + writeD(0x000003ea); + writeD(0x00); + writeD(0x00); + writeD(0x00); + writeB(new byte[16]); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayFail.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayFail.java new file mode 100644 index 00000000..b5f630db --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayFail.java @@ -0,0 +1,88 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +/** + * @version 2.6.1.0 + */ +public final class PlayFail extends L2LoginServerPacket { + public enum PlayFailReason { + REASON_NO_MESSAGE(0x00), + REASON_SYSTEM_ERROR_LOGIN_LATER(0x01), + REASON_USER_OR_PASS_WRONG(0x02), + REASON_ACCESS_FAILED_TRY_AGAIN_LATER(0x04), + REASON_ACCOUNT_INFO_INCORRECT_CONTACT_SUPPORT(0x05), + REASON_ACCOUNT_IN_USE(0x07), + REASON_UNDER_18_YEARS_KR(0x0C), + REASON_SERVER_OVERLOADED(0x0F), + REASON_SERVER_MAINTENANCE(0x10), + REASON_TEMP_PASS_EXPIRED(0x11), + REASON_GAME_TIME_EXPIRED(0x12), + REASON_NO_TIME_LEFT(0x13), + REASON_SYSTEM_ERROR(0x14), + REASON_ACCESS_FAILED(0x15), + REASON_RESTRICTED_IP(0x16), + REASON_WEEK_USAGE_FINISHED(0x1E), + REASON_SECURITY_CARD_NUMBER_INVALID(0x1F), + REASON_AGE_NOT_VERIFIED_CANT_LOG_BETWEEN_10PM_6AM(0x20), + REASON_SERVER_CANNOT_BE_ACCESSED_BY_YOUR_COUPON(0x21), + REASON_DUAL_BOX(0x23), + REASON_INACTIVE(0x24), + REASON_USER_AGREEMENT_REJECTED_ON_WEBSITE(0x25), + REASON_GUARDIAN_CONSENT_REQUIRED(0x26), + REASON_USER_AGREEMENT_DECLINED_OR_WITHDRAWAL_REQUEST(0x27), + REASON_ACCOUNT_SUSPENDED_CALL(0x28), + REASON_CHANGE_PASSWORD_AND_QUIZ_ON_WEBSITE(0x29), + REASON_ALREADY_LOGGED_INTO_10_ACCOUNTS(0x2A), + REASON_MASTER_ACCOUNT_RESTRICTED(0x2B), + REASON_CERTIFICATION_FAILED(0x2E), + REASON_TELEPHONE_CERTIFICATION_UNAVAILABLE(0x2F), + REASON_TELEPHONE_SIGNALS_DELAYED(0x30), + REASON_CERTIFICATION_FAILED_LINE_BUSY(0x31), + REASON_CERTIFICATION_SERVICE_NUMBER_EXPIRED_OR_INCORRECT(0x32), + REASON_CERTIFICATION_SERVICE_CURRENTLY_BEING_CHECKED(0x33), + REASON_CERTIFICATION_SERVICE_CANT_BE_USED_HEAVY_VOLUME(0x34), + REASON_CERTIFICATION_SERVICE_EXPIRED_GAMEPLAY_BLOCKED(0x35), + REASON_CERTIFICATION_FAILED_3_TIMES_GAMEPLAY_BLOCKED_30_MIN(0x36), + REASON_CERTIFICATION_DAILY_USE_EXCEEDED(0x37), + REASON_CERTIFICATION_UNDERWAY_TRY_AGAIN_LATER(0x38); + + private final int _code; + + PlayFailReason(int code) { + _code = code; + } + + public final int getCode() { + return _code; + } + } + + private final PlayFailReason _reason; + + public PlayFail(PlayFailReason reason) { + _reason = reason; + } + + @Override + protected void write() { + writeC(0x06); + writeC(_reason.getCode()); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayOk.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayOk.java new file mode 100644 index 00000000..edbe75da --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/PlayOk.java @@ -0,0 +1,40 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +import com.l2jserver.loginserver.SessionKey; + +/** + * @version 2.6.1.0 + */ +public final class PlayOk extends L2LoginServerPacket { + private final int _playOk1, _playOk2; + + public PlayOk(SessionKey sessionKey) { + _playOk1 = sessionKey.playOkID1; + _playOk2 = sessionKey.playOkID2; + } + + @Override + protected void write() { + writeC(0x07); + writeD(_playOk1); + writeD(_playOk2); + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/ServerList.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/ServerList.java new file mode 100644 index 00000000..12b5d117 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/network/serverpackets/ServerList.java @@ -0,0 +1,162 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.network.serverpackets; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.loginserver.GameServerTable; +import com.l2jserver.loginserver.GameServerTable.GameServerInfo; +import com.l2jserver.loginserver.network.L2LoginClient; +import com.l2jserver.loginserver.network.gameserverpackets.ServerStatus; + +/** + * ServerList + * + *
+ * Format: cc [cddcchhcdc]
+ * 
+ * c: server list size (number of servers)
+ * c: ?
+ * [ (repeat for each servers)
+ * c: server id (ignored by client?)
+ * d: server ip
+ * d: server port
+ * c: age limit (used by client?)
+ * c: pvp or not (used by client?)
+ * h: current number of players
+ * h: max number of players
+ * c: 0 if server is down
+ * d: 2nd bit: clock
+ *    3rd bit: won't display server name
+ *    4th bit: test server (used by client?)
+ * c: 0 if you don't want to display brackets in front of sever name
+ * ]
+ * 
+ * + * Server will be considered as Good when the number of online players
+ * is less than half the maximum. as Normal between half and 4/5
+ * and Full when there's more than 4/5 of the maximum number of players. + * @version 2.6.1.0 + */ +public final class ServerList extends L2LoginServerPacket { + + private static final Logger LOG = LoggerFactory.getLogger(ServerList.class); + + private final List _servers; + private final int _lastServer; + private final Map _charsOnServers; + private final Map _charsToDelete; + + static class ServerData { + protected byte[] _ip; + protected final int _port; + protected final int _ageLimit; + protected final boolean _pvp; + protected final int _currentPlayers; + protected final int _maxPlayers; + protected final boolean _brackets; + protected boolean _clock; + protected final int _status; + protected final int _serverId; + protected final int _serverType; + + ServerData(L2LoginClient client, GameServerInfo gsi) { + try { + _ip = InetAddress.getByName(gsi.getServerAddress(client.getConnection().getInetAddress())).getAddress(); + } catch (UnknownHostException ex) { + LOG.warn("There has been an error getting IP from host!", ex); + _ip = new byte[4]; + _ip[0] = 127; + _ip[1] = 0; + _ip[2] = 0; + _ip[3] = 1; + } + + _port = gsi.getPort(); + _pvp = gsi.isPvp(); + _serverType = gsi.getServerType(); + _currentPlayers = gsi.getCurrentPlayerCount(); + _maxPlayers = gsi.getMaxPlayers(); + _ageLimit = 0; + _brackets = gsi.isShowingBrackets(); + // If server GM-only - show status only to GMs + _status = gsi.getStatus() != ServerStatus.STATUS_GM_ONLY ? gsi.getStatus() : client.getAccessLevel() > 0 ? gsi.getStatus() : ServerStatus.STATUS_DOWN; + _serverId = gsi.getId(); + } + } + + public ServerList(L2LoginClient client) { + _servers = new ArrayList<>(GameServerTable.getInstance().getRegisteredGameServers().size()); + _lastServer = client.getLastServer(); + for (GameServerInfo gsi : GameServerTable.getInstance().getRegisteredGameServers().values()) { + _servers.add(new ServerData(client, gsi)); + } + _charsOnServers = client.getCharsOnServ(); + _charsToDelete = client.getCharsWaitingDelOnServ(); + } + + @Override + public void write() { + writeC(0x04); + writeC(_servers.size()); + writeC(_lastServer); + for (ServerData server : _servers) { + writeC(server._serverId); // server id + + writeC(server._ip[0] & 0xff); + writeC(server._ip[1] & 0xff); + writeC(server._ip[2] & 0xff); + writeC(server._ip[3] & 0xff); + + writeD(server._port); + writeC(server._ageLimit); // Age Limit 0, 15, 18 + writeC(server._pvp ? 0x01 : 0x00); + writeH(server._currentPlayers); + writeH(server._maxPlayers); + writeC(server._status == ServerStatus.STATUS_DOWN ? 0x00 : 0x01); + writeD(server._serverType); // 1: Normal, 2: Relax, 4: Public Test, 8: No Label, 16: Character Creation Restricted, 32: Event, 64: Free + writeC(server._brackets ? 0x01 : 0x00); + } + writeH(0x00); // unknown + if (_charsOnServers != null) { + writeC(_charsOnServers.size()); + for (int servId : _charsOnServers.keySet()) { + writeC(servId); + writeC(_charsOnServers.get(servId)); + if ((_charsToDelete == null) || !_charsToDelete.containsKey(servId)) { + writeC(0x00); + } else { + writeC(_charsToDelete.get(servId).length); + for (long deleteTime : _charsToDelete.get(servId)) { + writeD((int) ((deleteTime - System.currentTimeMillis()) / 1000)); + } + } + } + } else { + writeC(0x00); + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/ScrambledKeyPair.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/ScrambledKeyPair.java new file mode 100644 index 00000000..e7790c95 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/ScrambledKeyPair.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.security; + +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.interfaces.RSAPublicKey; + +public class ScrambledKeyPair { + + private final KeyPair pair; + + private final byte[] scrambledModulus; + + public ScrambledKeyPair(KeyPair pPair) { + pair = pPair; + scrambledModulus = scrambleModulus(((RSAPublicKey) pair.getPublic()).getModulus()); + } + + public KeyPair getPair() { + return pair; + } + + public byte[] getScrambledModulus() { + return scrambledModulus; + } + + private byte[] scrambleModulus(BigInteger modulus) { + byte[] scrambledMod = modulus.toByteArray(); + + if ((scrambledMod.length == 0x81) && (scrambledMod[0] == 0x00)) { + byte[] temp = new byte[0x80]; + System.arraycopy(scrambledMod, 1, temp, 0, 0x80); + scrambledMod = temp; + } + // step 1 : 0x4d-0x50 <-> 0x00-0x04 + for (int i = 0; i < 4; i++) { + byte temp = scrambledMod[i]; + scrambledMod[i] = scrambledMod[0x4d + i]; + scrambledMod[0x4d + i] = temp; + } + // step 2 : xor first 0x40 bytes with last 0x40 bytes + for (int i = 0; i < 0x40; i++) { + scrambledMod[i] = (byte) (scrambledMod[i] ^ scrambledMod[0x40 + i]); + } + // step 3 : xor bytes 0x0d-0x10 with bytes 0x34-0x38 + for (int i = 0; i < 4; i++) { + scrambledMod[0x0d + i] = (byte) (scrambledMod[0x0d + i] ^ scrambledMod[0x34 + i]); + } + // step 4 : xor last 0x40 bytes with first 0x40 bytes + for (int i = 0; i < 0x40; i++) { + scrambledMod[0x40 + i] = (byte) (scrambledMod[0x40 + i] ^ scrambledMod[i]); + } + return scrambledMod; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/crypt/LoginCrypt.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/crypt/LoginCrypt.java new file mode 100644 index 00000000..19db5bf8 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/security/crypt/LoginCrypt.java @@ -0,0 +1,117 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.security.crypt; + +import java.io.IOException; + +import com.l2jserver.commons.security.crypt.NewCrypt; +import com.l2jserver.commons.util.Rnd; + +/** + * @author KenM + */ +public class LoginCrypt { + private static final byte[] STATIC_BLOWFISH_KEY = { + (byte) 0x6b, + (byte) 0x60, + (byte) 0xcb, + (byte) 0x5b, + (byte) 0x82, + (byte) 0xce, + (byte) 0x90, + (byte) 0xb1, + (byte) 0xcc, + (byte) 0x2b, + (byte) 0x6c, + (byte) 0x55, + (byte) 0x6c, + (byte) 0x6c, + (byte) 0x6c, + (byte) 0x6c + }; + + private static final NewCrypt _STATIC_CRYPT = new NewCrypt(STATIC_BLOWFISH_KEY); + private NewCrypt _crypt = null; + private boolean _static = true; + + /** + * Method to initialize the blowfish cipher with dynamic key. + * @param key the blowfish key to initialize the dynamic blowfish cipher with + */ + public void setKey(byte[] key) { + _crypt = new NewCrypt(key); + } + + /** + * Method to decrypt an incoming login client packet. + * @param raw array with encrypted data + * @param offset offset where the encrypted data is located + * @param size number of bytes of encrypted data + * @return true when checksum could be verified, false otherwise + * @throws IOException the size is not multiple of blowfish block size or the raw array can't hold size bytes starting at offset due to its size + */ + public boolean decrypt(byte[] raw, final int offset, final int size) throws IOException { + if ((size % 8) != 0) { + throw new IOException("size have to be multiple of 8"); + } + if ((offset + size) > raw.length) { + throw new IOException("raw array too short for size starting from offset"); + } + + _crypt.decrypt(raw, offset, size); + return NewCrypt.verifyChecksum(raw, offset, size); + } + + /** + * Method to encrypt an outgoing packet to login client.
+ * Performs padding and resizing of data array. + * @param raw array with plain data + * @param offset offset where the plain data is located + * @param size number of bytes of plain data + * @return the new array size + * @throws IOException packet is too long to make padding and add verification data + */ + public int encrypt(byte[] raw, final int offset, int size) throws IOException { + // reserve checksum + size += 4; + + if (_static) { + // reserve for XOR "key" + size += 4; + + // padding + size += 8 - (size % 8); + if ((offset + size) > raw.length) { + throw new IOException("packet too long"); + } + NewCrypt.encXORPass(raw, offset, size, Rnd.nextInt()); + _STATIC_CRYPT.crypt(raw, offset, size); + _static = false; + } else { + // padding + size += 8 - (size % 8); + if ((offset + size) > raw.length) { + throw new IOException("packet too long"); + } + NewCrypt.appendChecksum(raw, offset, size); + _crypt.crypt(raw, offset, size); + } + return size; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/LoginStatusThread.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/LoginStatusThread.java new file mode 100644 index 00000000..fcddb9a7 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/LoginStatusThread.java @@ -0,0 +1,216 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.status; + +import static com.l2jserver.loginserver.config.Configuration.server; +import static com.l2jserver.loginserver.config.Configuration.telnet; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.loginserver.GameServerTable; +import com.l2jserver.loginserver.LoginController; +import com.l2jserver.loginserver.LoginServer; + +public final class LoginStatusThread extends Thread { + + private static final Logger LOG = LoggerFactory.getLogger(LoginStatusThread.class); + + private final Socket _cSocket; + + private final PrintWriter _print; + + private final BufferedReader _read; + + private boolean _redirectLogger; + + private void telnetOutput(int type, String text) { + if (type == 1) { + System.out.println("TELNET | " + text); + } else if (type == 2) { + System.out.print("TELNET | " + text); + } else if (type == 3) { + System.out.print(text); + } else if (type == 4) { + System.out.println(text); + } else { + System.out.println("TELNET | " + text); + } + } + + private boolean isValidIP(Socket client) { + boolean result = false; + InetAddress clientIP = client.getInetAddress(); + + // convert IP to String, and compare with list + String clientStringIP = clientIP.getHostAddress(); + + telnetOutput(1, "Connection from: " + clientStringIP); + + // read and loop through a list of IPs, compare with them to the newIP + if (server().isDebug()) { + telnetOutput(2, ""); + } + + if (server().isDebug()) { + telnetOutput(3, "Comparing ip to list..."); + } + + for (String host : telnet().getHosts()) { + try { + String ipToCompare = InetAddress.getByName(host).getHostAddress(); + if (clientStringIP.equals(ipToCompare)) { + result = true; + } + + if (server().isDebug()) { + telnetOutput(3, clientStringIP + " = " + ipToCompare + "(" + host + ") = " + result); + } + } catch (Exception ex) { + LOG.warn("There has been an error parsing host {}!", host, ex); + } + } + + if (server().isDebug()) { + telnetOutput(4, "Allow IP: " + result); + } + return result; + } + + public LoginStatusThread(Socket client, int uptime, String StatusPW) throws Exception { + _cSocket = client; + + _print = new PrintWriter(_cSocket.getOutputStream()); + _read = new BufferedReader(new InputStreamReader(_cSocket.getInputStream())); + + if (isValidIP(client)) { + telnetOutput(1, client.getInetAddress().getHostAddress() + " accepted."); + _print.println("Welcome To The L2J Telnet Session."); + _print.println("Please Insert Your Password!"); + _print.print("Password: "); + _print.flush(); + String tmpLine = _read.readLine(); + if (tmpLine == null) { + _print.println("Error."); + _print.println("Disconnected..."); + _print.flush(); + _cSocket.close(); + } else { + if (!tmpLine.equals(StatusPW)) { + _print.println("Incorrect Password!"); + _print.println("Disconnected..."); + _print.flush(); + _cSocket.close(); + } else { + _print.println("Password Correct!"); + _print.println("[L2J Login Server]"); + _print.print(""); + _print.flush(); + start(); + } + } + } else { + telnetOutput(5, "Connection attempt from " + client.getInetAddress().getHostAddress() + " rejected."); + _cSocket.close(); + } + } + + @Override + public void run() { + String _usrCommand = ""; + try { + while ((_usrCommand.compareTo("quit") != 0) && (_usrCommand.compareTo("exit") != 0)) { + _usrCommand = _read.readLine(); + if (_usrCommand == null) { + _cSocket.close(); + break; + } + if (_usrCommand.equals("help")) { + _print.println("The following is a list of all available commands: "); + _print.println("help - shows this help."); + _print.println("status - displays basic server statistics."); + _print.println("unblock - removes from the ban list."); + _print.println("shutdown - shuts down server."); + _print.println("restart - restarts the server."); + _print.println("RedirectLogger - Telnet will give you some info about server in real time."); + _print.println("quit - closes telnet session."); + _print.println(""); + } else if (_usrCommand.equals("status")) { + // TODO enhance the output + _print.println("Registered Server Count: " + GameServerTable.getInstance().getRegisteredGameServers().size()); + } else if (_usrCommand.startsWith("unblock")) { + try { + _usrCommand = _usrCommand.substring(8); + if (LoginController.getInstance().removeBanForAddress(_usrCommand)) { + LOG.warn("IP removed via TELNET by host {}!", _cSocket.getInetAddress().getHostAddress()); + _print.println("The IP " + _usrCommand + " has been removed from the hack protection list!"); + } else { + _print.println("IP not found in hack protection list..."); + } + } catch (StringIndexOutOfBoundsException e) { + _print.println("Please Enter the IP to Unblock!"); + } + } else if (_usrCommand.startsWith("shutdown")) { + LoginServer.getInstance().shutdown(false); + _print.println("Bye Bye!"); + _print.flush(); + _cSocket.close(); + } else if (_usrCommand.startsWith("restart")) { + LoginServer.getInstance().shutdown(true); + _print.println("Bye Bye!"); + _print.flush(); + _cSocket.close(); + } else if (_usrCommand.equals("RedirectLogger")) { + _redirectLogger = true; + } else if (_usrCommand.equals("quit")) { /* Do Nothing :p - Just here to save us from the "Command Not Understood" Text */ + } else if (_usrCommand.isEmpty()) { /* Do Nothing Again - Same reason as the quit part */ + } else { + _print.println("Invalid Command"); + } + _print.print(""); + _print.flush(); + } + if (!_cSocket.isClosed()) { + _print.println("Bye Bye!"); + _print.flush(); + _cSocket.close(); + } + telnetOutput(1, "Connection from " + _cSocket.getInetAddress().getHostAddress() + " was closed by client."); + } catch (Exception ex) { + LOG.warn("There has been an error executing login status task!", ex); + } + } + + public void printToTelnet(String msg) { + synchronized (_print) { + _print.println(msg); + _print.flush(); + } + } + + public boolean isRedirectLogger() { + return _redirectLogger; + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/Status.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/Status.java new file mode 100644 index 00000000..d4c45c48 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/status/Status.java @@ -0,0 +1,103 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.status; + +import static com.l2jserver.loginserver.config.Configuration.telnet; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.LinkedList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.l2jserver.commons.util.Util; + +public class Status extends Thread { + + private static final Logger LOG = LoggerFactory.getLogger(Status.class); + + private final ServerSocket statusServerSocket; + + private final int _uptime; + + private String _statusPw; + + private final List _loginStatus; + + public Status() throws IOException { + super("Status"); + _statusPw = telnet().getPassword(); + + if (_statusPw == null) { + _statusPw = Util.randomPassword(10); + LOG.info("Server's Telnet function has no password defined!"); + LOG.info("A password has been automatically created!"); + LOG.info("Password has been set to: {}", _statusPw); + } + + statusServerSocket = new ServerSocket(telnet().getPort()); + _uptime = (int) System.currentTimeMillis(); + _loginStatus = new LinkedList<>(); + LOG.info("Telnet server started successfully, listening on port {}.", telnet().getPort()); + } + + @Override + public void run() { + setPriority(Thread.MAX_PRIORITY); + + while (!isInterrupted()) { + try { + Socket connection = statusServerSocket.accept(); + LoginStatusThread lst = new LoginStatusThread(connection, _uptime, _statusPw); + if (lst.isAlive()) { + _loginStatus.add(lst); + } + + if (isInterrupted()) { + try { + statusServerSocket.close(); + } catch (Exception ex) { + LOG.warn("There has been an error closing status server socket!", ex); + } + break; + } + } catch (Exception ex1) { + if (isInterrupted()) { + try { + statusServerSocket.close(); + } catch (Exception ex2) { + LOG.warn("There has been an error closing status server socket!", ex2); + } + break; + } + } + } + } + + public void sendMessageToTelnets(String msg) { + for (LoginStatusThread ls : _loginStatus) { + if (!ls.isInterrupted()) { + ls.printToTelnet(msg); + } + } + } +} diff --git a/l2j-server-login/src/main/java/com/l2jserver/loginserver/util/IPSubnet.java b/l2j-server-login/src/main/java/com/l2jserver/loginserver/util/IPSubnet.java new file mode 100644 index 00000000..370a5733 --- /dev/null +++ b/l2j-server-login/src/main/java/com/l2jserver/loginserver/util/IPSubnet.java @@ -0,0 +1,132 @@ +/* + * Copyright © 2004-2026 L2J Server + * + * This file is part of L2J Server. + * + * L2J Server is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * L2J Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.l2jserver.loginserver.util; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; + +public class IPSubnet { + final byte[] _addr; + final byte[] _mask; + final boolean _isIPv4; + + public IPSubnet(String input) throws UnknownHostException, NumberFormatException, ArrayIndexOutOfBoundsException { + int idx = input.indexOf("/"); + if (idx > 0) { + _addr = InetAddress.getByName(input.substring(0, idx)).getAddress(); + _mask = getMask(Integer.parseInt(input.substring(idx + 1)), _addr.length); + _isIPv4 = _addr.length == 4; + + if (!applyMask(_addr)) { + throw new UnknownHostException(input); + } + } else { + _addr = InetAddress.getByName(input).getAddress(); + _mask = getMask(_addr.length * 8, _addr.length); // host, no need to check mask + _isIPv4 = _addr.length == 4; + } + } + + public IPSubnet(InetAddress addr, int mask) throws UnknownHostException { + _addr = addr.getAddress(); + _isIPv4 = _addr.length == 4; + _mask = getMask(mask, _addr.length); + if (!applyMask(_addr)) { + throw new UnknownHostException(addr + "/" + mask); + } + } + + public byte[] getAddress() { + return _addr; + } + + public boolean applyMask(byte[] addr) { + // V4 vs V4 or V6 vs V6 checks + if (_isIPv4 == (addr.length == 4)) { + for (int i = 0; i < _addr.length; i++) { + if ((addr[i] & _mask[i]) != _addr[i]) { + return false; + } + } + } else { + // check for embedded v4 in v6 addr (not done !) + if (_isIPv4) { + // my V4 vs V6 + for (int i = 0; i < _addr.length; i++) { + if ((addr[i + 12] & _mask[i]) != _addr[i]) { + return false; + } + } + } else { + // my V6 vs V4 + for (int i = 0; i < _addr.length; i++) { + if ((addr[i] & _mask[i + 12]) != _addr[i + 12]) { + return false; + } + } + } + } + + return true; + } + + @Override + public String toString() { + int size = 0; + for (byte element : _mask) { + size += Integer.bitCount((element & 0xFF)); + } + + try { + return InetAddress.getByAddress(_addr) + "/" + size; + } catch (UnknownHostException e) { + return "Invalid"; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o instanceof IPSubnet) { + return applyMask(((IPSubnet) o).getAddress()); + } else if (o instanceof InetAddress) { + return applyMask(((InetAddress) o).getAddress()); + } + + return false; + } + + private static byte[] getMask(int n, int maxLength) throws UnknownHostException { + if ((n > (maxLength << 3)) || (n < 0)) { + throw new UnknownHostException("Invalid netmask: " + n); + } + + final byte[] result = new byte[maxLength]; + Arrays.fill(result, (byte) 0xFF); + + for (int i = (maxLength << 3) - 1; i >= n; i--) { + result[i >> 3] = (byte) (result[i >> 3] << 1); + } + + return result; + } +} \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/LoginServer_loop.sh b/l2j-server-login/src/main/resources/LoginServer_loop.sh new file mode 100644 index 00000000..db1bdfa1 --- /dev/null +++ b/l2j-server-login/src/main/resources/LoginServer_loop.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +err=1 +until [ $err -eq 0 ]; +do + [ -f log/java0.log.0 ] && mv log/java0.log.0 "log/`date +%Y-%m-%d_%H-%M-%S`_java.log" + [ -f log/stdout.log ] && mv log/stdout.log "log/`date +%Y-%m-%d_%H-%M-%S`_stdout.log" + java -Xms128m -Xmx256m -jar l2jlogin.jar >> log/stdout.log 2>&1 + err=$? + sleep 10; +done diff --git a/l2j-server-login/src/main/resources/config/banned_ip.cfg b/l2j-server-login/src/main/resources/config/banned_ip.cfg new file mode 100644 index 00000000..ee951522 --- /dev/null +++ b/l2j-server-login/src/main/resources/config/banned_ip.cfg @@ -0,0 +1,7 @@ +# Subnets can be defined like 123.45.0.0 +# Comments after # are ignored +# Expiration timestamp can be added after IP for when to disable the ban +# Example: +# 192.168.0.0 1237618800000 # Bans all address starting with 192.168 and will expire on 21 Mar 2009 07:00:00 GMT +10.255.255.255 +10.254.254.254 diff --git a/l2j-server-login/src/main/resources/config/database.properties b/l2j-server-login/src/main/resources/config/database.properties new file mode 100644 index 00000000..fbb9fb17 --- /dev/null +++ b/l2j-server-login/src/main/resources/config/database.properties @@ -0,0 +1,21 @@ +# --------------------------------------------------------------------------- +# Database +# --------------------------------------------------------------------------- + +# Database URL +# Default: jdbc:mariadb://localhost/l2jls +URL = jdbc:mariadb://localhost/l2jls + +# Database User +# Default: l2j +User = l2j + +# Database Password +# Default: l2jserver2019 +Password = l2jserver2019 + +# Default: 10 +MaxConnections = 10 + +# Default: 0 +MaxIdleTime = 0 \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/config/email.properties b/l2j-server-login/src/main/resources/config/email.properties new file mode 100644 index 00000000..5688561d --- /dev/null +++ b/l2j-server-login/src/main/resources/config/email.properties @@ -0,0 +1,65 @@ +# --------------------------------------------------------------------------- +# Email Settings +# --------------------------------------------------------------------------- +# The defaults are set to be retail-like. If you modify any of these settings your server will deviate from being retail-like. +# Warning: +# Please take extreme caution when changing anything. Also, please understand what you are changing before you do so on a live server. +# --------------------------------------------------------------------------- +# Server Information +# --------------------------------------------------------------------------- + +# Server Name +# Default: L2J Server +ServerName = L2J Server + +# Contact Address +# Default: info@l2jserver.com +ServerEmail = info@l2jserver.com + +# --------------------------------------------------------------------------- +# E-mail System Settings +# --------------------------------------------------------------------------- + +# Enable Email System +# Default: False +EnableEmail = False + +# Select Query for Email Addresses +# Default: SELECT value FROM account_data WHERE account_name=? AND var='email_addr' +EmailSelectQuery = SELECT value FROM account_data WHERE account_name=? AND var='email_addr' + +# Email Address Field +# Default: value +DatabaseField = value + +# Mail Server Host +# Default: smtp.gmail.com +Host = smtp.gmail.com + +# Mail Server Port +# Default: 465 +Port = 465 + +# Auth SMTP +# Default: True +SmtpAuthRequired = True + +# Mail Socket Factory +# Default: javax.net.ssl.SSLSocketFactory +SmtpFactory = javax.net.ssl.SSLSocketFactory + +# Mail Factory Callback +# Default: False +SmtpFactoryCallback = False + +# Mail Server Auth - Username +# Default: user@gmail.com +SmtpUsername = user@gmail.com + +# Mail Server Auth - Password +# Default: password +SmtpPassword = password + +# Mail Address +# Default: noreply@l2jserver.com +SystemAddress = noreply@l2jserver.com \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/config/mmo.properties b/l2j-server-login/src/main/resources/config/mmo.properties new file mode 100644 index 00000000..5e48a3d3 --- /dev/null +++ b/l2j-server-login/src/main/resources/config/mmo.properties @@ -0,0 +1,24 @@ +#--------------------------------------------------------------- +# MMO +#--------------------------------------------------------------- + +# Sleep time for all Selectors +# After he finished his job the Selector waits the given time in milliseconds +# Lower values will speed up the loop and the Ping is smaller +SleepTime = 20 + +# Every loop, it sends a maximum of the given packages to each connection +# Lower values will speed up the loop and the Ping is smaller but cause less output +MaxSendPerPass = 12 + +# Every loop it read a maximum of the given packages from each connection +# Lower values will speed up the loop and the Ping is smaller but cause less input +MaxReadPerPass = 12 + +# Each unfinished read/write need a TEMP storage Buffer +# on large player amount we need more Buffers +# if there are not enough buffers new ones are generated but not stored for future usage +HelperBufferCount = 20 + +# Setting this to True will lower your ping, at the cost of an increase in bandwidth consumption. +TcpNoDelay = False \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/config/server.properties b/l2j-server-login/src/main/resources/config/server.properties new file mode 100644 index 00000000..79df5490 --- /dev/null +++ b/l2j-server-login/src/main/resources/config/server.properties @@ -0,0 +1,116 @@ +# --------------------------------------------------------------------------- +# Login Server Settings +# --------------------------------------------------------------------------- +# This is the server configuration file. Here you can set up the connection information for your server. +# This was written with the assumption that you are behind a router. +# Definitions: +# LAN (Local area network) - typically consists of computers connected to the same router as you. +# WAN (Wide area network) - typically consists of computers OUTSIDE your router (i.e. the Internet). +# x.x.x.x - Format of an IP address. Do not include the xes into settings. Must be real numbers. +# --------------------------------------------------------------------------- +# Networking +# --------------------------------------------------------------------------- + +# Enables automatic port mapping for login server. +# If you have a router login server will request for port forwarding. +# Default: True +EnableUPnP = True + +# Bind IP of the LoginServer, use * to bind on all available IPs +# WARNING: Please don't change default IPs here if you don't know what are you doing! +# WARNING: External/Internal IPs are now inside "ipconfig.xml" file. +# Default: * (0.0.0.0) +Host = * + +# Default: 2106 +Port = 2106 + +# The address on which login will listen for GameServers, use * to bind on all available IPs +# WARNING: Please don't change default IPs here if you don't know what are you doing! +# WARNING: External/Internal IPs are now inside "ipconfig.xml" file. +# Default: 127.0.0.1 +GameServerHost = 127.0.0.1 + +# The port on which login will listen for GameServers +# Default: 9014 +GameServerPort = 9014 + +# Allowed Server Versions +# Default: 6, 14 +ServerVersions = 6, 14 + +# --------------------------------------------------------------------------- +# Security +# --------------------------------------------------------------------------- + +# How many times you can provide an invalid account/pass before the IP gets banned. +# Default: 5 +LoginTryBeforeBan = 5 + +# Time you won't be able to log in back again after LoginTryBeforeBan tries to login. +# Default: 900 (15 minutes) +LoginBlockAfterBan = 900 + +# If set to True any GameServer can register on your login's free slots +# Default: True +AcceptNewGameServer = True + +# Flood Protection. All values are in milliseconds. +# Default: True +EnableFloodProtection = True + +# Default: 15 +FastConnectionLimit = 15 + +# Default: 700 +NormalConnectionTime = 700 + +# Default: 350 +FastConnectionTime = 350 + +# Default: 50 +MaxConnectionPerIP = 50 + +# --------------------------------------------------------------------------- +# Well known access levels. Access levels specified here must be negative. 0 +# is always a normal user and positive is a GM. +# --------------------------------------------------------------------------- +# Default: -127 +AccountInactiveAccessLevel = -127 + +# --------------------------------------------------------------------------- +# Misc. +# --------------------------------------------------------------------------- + +# If False, the license (after the login) will not be shown. +# Default: True +ShowLicence = True + +# Default: True +AutoCreateAccounts = True + +# Specifies the access level which is set for auto created accounts. +# Default: 0 +AutoCreateAccountsAccessLevel = 0 + +# Datapack root directory. +# Defaults to current directory from which the server is started. +DatapackRoot = . + +# --------------------------------------------------------------------------- +# Developer Settings +# --------------------------------------------------------------------------- + +# Default: False +Debug = False + +# --------------------------------------------------------------------------- +# Restart LS every 24 hours? +# --------------------------------------------------------------------------- + +# Enable disable scheduled login restart. +# Default: False +LoginRestartSchedule = False +# Time in hours. +# Default: 24 +LoginRestartTime = 24 \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/config/telnet.properties b/l2j-server-login/src/main/resources/config/telnet.properties new file mode 100644 index 00000000..efa42dde --- /dev/null +++ b/l2j-server-login/src/main/resources/config/telnet.properties @@ -0,0 +1,28 @@ +# --------------------------------------------------------------------------- +# Telnet Settings +# --------------------------------------------------------------------------- +# The defaults are set to be retail-like. If you modify any of these settings your server will deviate from being retail-like. +# Warning: +# Please take extreme caution when changing anything. Also, please understand what you are changing before you do so on a live server. +# --------------------------------------------------------------------------- + +# Allows text based monitoring and administration of L2J LS +# by using a telnet client. Communication protocol is insecure, +# and you should use SSL tunnels, VPN, etc. if you plan to connect +# over non-trusted channels. +# Default: False +EnableTelnet = False + +# This is the port L2J should listen to for incoming telnet +# requests. +# Default: 12345 +Port = 12345 + +# If the following is not set, a random password is generated on server startup. +# Usage: Password = somePass +Password = + +# This list can contain IPs or Hosts of clients you wish to allow. Hostnames must be resolvable to an IP. +# Example: 0.0.0.0,host,0.0.0.1,host2,host3,host4,0.0.0.3 +# Default: 127.0.0.1,localhost +Hosts = 127.0.0.1,localhost \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/data/mail/MailList.xml b/l2j-server-login/src/main/resources/data/mail/MailList.xml new file mode 100644 index 00000000..4fed34e1 --- /dev/null +++ b/l2j-server-login/src/main/resources/data/mail/MailList.xml @@ -0,0 +1,4 @@ + + + + diff --git a/l2j-server-login/src/main/resources/data/mail/MailList.xsd b/l2j-server-login/src/main/resources/data/mail/MailList.xsd new file mode 100644 index 00000000..d13b894e --- /dev/null +++ b/l2j-server-login/src/main/resources/data/mail/MailList.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/data/mail/html/SecAuthTempBan.htm b/l2j-server-login/src/main/resources/data/mail/html/SecAuthTempBan.htm new file mode 100644 index 00000000..cc2dfbfc --- /dev/null +++ b/l2j-server-login/src/main/resources/data/mail/html/SecAuthTempBan.htm @@ -0,0 +1,15 @@ +Greetings,
+
+We have temporally suspended your account:
+
+Account Name: %accountname%
+Reason: Typed wrong character password %var1% times in row from ip %var0%.
+Consequences: Your account has been temporally suspended for %var2% minutes.
+
+If you've lost your character password, you can recover them by clicking here.
+
+If you have any question, click here to email an administrator.
+Regards,
+%servername% Game Master Team
+
+

Powered By: L2J Server

diff --git a/l2j-server-login/src/main/resources/data/servername.xml b/l2j-server-login/src/main/resources/data/servername.xml new file mode 100644 index 00000000..a4247445 --- /dev/null +++ b/l2j-server-login/src/main/resources/data/servername.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/data/servername.xsd b/l2j-server-login/src/main/resources/data/servername.xsd new file mode 100644 index 00000000..13f9c223 --- /dev/null +++ b/l2j-server-login/src/main/resources/data/servername.xsd @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/l2j-server-login-logo_256x256.png b/l2j-server-login/src/main/resources/l2j-server-login-logo_256x256.png new file mode 100644 index 00000000..99371616 Binary files /dev/null and b/l2j-server-login/src/main/resources/l2j-server-login-logo_256x256.png differ diff --git a/l2j-server-login/src/main/resources/log4j2.xml b/l2j-server-login/src/main/resources/log4j2.xml new file mode 100644 index 00000000..b67d9b40 --- /dev/null +++ b/l2j-server-login/src/main/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/sql/account_data.sql b/l2j-server-login/src/main/resources/sql/account_data.sql new file mode 100644 index 00000000..e4b0c887 --- /dev/null +++ b/l2j-server-login/src/main/resources/sql/account_data.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `account_data` ( + `account_name` VARCHAR(45) NOT NULL DEFAULT '', + `var` VARCHAR(20) NOT NULL DEFAULT '', + `value` VARCHAR(255) , + PRIMARY KEY (`account_name`,`var`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/sql/accounts.sql b/l2j-server-login/src/main/resources/sql/accounts.sql new file mode 100644 index 00000000..ef5b3110 --- /dev/null +++ b/l2j-server-login/src/main/resources/sql/accounts.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS `accounts` ( + `login` VARCHAR(45) NOT NULL default '', + `password` VARCHAR(45), + `email` varchar(255) DEFAULT NULL, + `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `lastactive` bigint(13) unsigned NOT NULL DEFAULT '0', + `accessLevel` TINYINT NOT NULL DEFAULT 0, + `lastIP` CHAR(15) NULL DEFAULT NULL, + `lastServer` TINYINT DEFAULT 1, + `pcIp` char(15) DEFAULT NULL, + `hop1` char(15) DEFAULT NULL, + `hop2` char(15) DEFAULT NULL, + `hop3` char(15) DEFAULT NULL, + `hop4` char(15) DEFAULT NULL, + PRIMARY KEY (`login`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/sql/accounts_ipauth.sql b/l2j-server-login/src/main/resources/sql/accounts_ipauth.sql new file mode 100644 index 00000000..ac24ff7d --- /dev/null +++ b/l2j-server-login/src/main/resources/sql/accounts_ipauth.sql @@ -0,0 +1,5 @@ +CREATE TABLE IF NOT EXISTS `accounts_ipauth` ( + `login` varchar(45) NOT NULL, + `ip` char(15) NOT NULL, + `type` enum('deny','allow') NULL DEFAULT 'allow' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/sql/cleanup/cleanup.sql b/l2j-server-login/src/main/resources/sql/cleanup/cleanup.sql new file mode 100644 index 00000000..8bf8d961 --- /dev/null +++ b/l2j-server-login/src/main/resources/sql/cleanup/cleanup.sql @@ -0,0 +1,10 @@ +-- These queries are meant to delete any login server table you may have from previous L2J installations. +-- +-- Queries that create these tables and/or populate them appropriately are +-- available in the ../sql/ folder. See the documentation.txt file for more details. + +DROP TABLE IF EXISTS +account_data, +accounts, +accounts_ipauth, +gameservers; \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/sql/gameservers.sql b/l2j-server-login/src/main/resources/sql/gameservers.sql new file mode 100644 index 00000000..a8e24819 --- /dev/null +++ b/l2j-server-login/src/main/resources/sql/gameservers.sql @@ -0,0 +1,6 @@ +CREATE TABLE IF NOT EXISTS `gameservers` ( + `server_id` int(11) NOT NULL DEFAULT '0', + `hexid` varchar(50) NOT NULL DEFAULT '', + `host` varchar(50) NOT NULL DEFAULT '', + PRIMARY KEY (`server_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/startLoginServer.bat b/l2j-server-login/src/main/resources/startLoginServer.bat new file mode 100644 index 00000000..3b022e13 --- /dev/null +++ b/l2j-server-login/src/main/resources/startLoginServer.bat @@ -0,0 +1,29 @@ +@echo off +title Login Server Console + +:start +echo Starting L2J Login Server. +echo. + +java -Xms128m -Xmx256m -jar l2jlogin.jar + +if ERRORLEVEL 2 goto restart +if ERRORLEVEL 1 goto error +goto end + +:restart +echo. +echo Admin Restarted Login Server. +echo. +goto start + +:error +echo. +echo Login Server terminated abnormally! +echo. + +:end +echo. +echo Login Server Terminated. +echo. +pause \ No newline at end of file diff --git a/l2j-server-login/src/main/resources/startLoginServer.sh b/l2j-server-login/src/main/resources/startLoginServer.sh new file mode 100644 index 00000000..90e07a64 --- /dev/null +++ b/l2j-server-login/src/main/resources/startLoginServer.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +./LoginServer_loop.sh &