commit fd94a4e3c4be2ea9530f97c96a267227ae4333f3 Author: Matthew Saunders Brown Date: Fri Jul 1 15:24:26 2022 -0700 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a27a91c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +panel/f3/ +panel/tmp/* diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + 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 +. + + 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 +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aadedee --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# vPanel Stack + +Web Panel admin for vhost-stack & vmail-stack built with F3 (Fat-Free Framework) + +## Install +``` +cd /usr/local/src/ +git clone https://git.stack-source.com/msb/vpanel-stack.git +cd vpanel-stack +bash install.sh +``` diff --git a/bin/vpanel-verify-access.sh b/bin/vpanel-verify-access.sh new file mode 100755 index 0000000..5f927b5 --- /dev/null +++ b/bin/vpanel-verify-access.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# vpanel-stack +# https://git.stack-source.com/msb/vpanel-stack +# Copyright (c) 2022 Matthew Saunders Brown +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# first switch to root +if [ "$USER" != "root" ]; then + exec sudo -u root $0 $@ +fi + +help() +{ + thisfilename=$(basename -- "$0") + echo "Check if specified user has vpanel sudo access." + echo "" + echo "usage: $thisfilename -u [-h]" + echo "" + echo " -u Username to check." + echo " -h Print this help." +} + +while getopts "hu:" opt ; do + case "${opt}" in + h ) # display help and exit + help + exit + ;; + u ) # username + username=${OPTARG,,} + ;; + \? ) + echo "Invalid option: $OPTARG" + exit 1 + ;; + : ) + echo "Invalid option: $OPTARG requires an argument" + exit 1 + ;; + esac +done + +# check for username +if [[ -z $username ]]; then + echo "username is required" + exit 1 +fi + +# check sudo rights +if /usr/bin/sudo -l -U $username vpanel-verify-access.sh >/dev/null; then + echo TRUE + exit 0 +else + echo FALSE + exit 1 +fi + diff --git a/etc/apache2/conf-available/vpanel.conf b/etc/apache2/conf-available/vpanel.conf new file mode 100644 index 0000000..dbac23a --- /dev/null +++ b/etc/apache2/conf-available/vpanel.conf @@ -0,0 +1,42 @@ +Alias /panel /srv/www/html/panel + + + + # panel php-fpm worker + + SetHandler "proxy:unix:/run/php/php7.4-fpm-vpanel.sock|fcgi://localhost" + + + # Enable rewrite engine + RewriteEngine On + + # force https + RewriteCond %{HTTPS} off + RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + + # Some servers require you to specify the `RewriteBase` directive + # In such cases, it should be the path (relative to the document root) + # containing the index file + RewriteBase /panel + + # return 404 Not Found (effectively blocking access) to: + # classes/ + # config/ + # f3/ + # tmp/ + # all hidden files and dirs (anything beginning with a .) + RewriteRule ^(classes|config|f3|tmp)\/|^\.|^/\. - [R=404] + + # deny direct access to html (template) files + + Deny from all + + + # Route requests to the framework + RewriteCond %{REQUEST_FILENAME} !-l + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule .* index.php [L,QSA] + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + + diff --git a/etc/php/7.4/fpm/pool.d/vpanel.conf b/etc/php/7.4/fpm/pool.d/vpanel.conf new file mode 100644 index 0000000..5cf5571 --- /dev/null +++ b/etc/php/7.4/fpm/pool.d/vpanel.conf @@ -0,0 +1,10 @@ +[vpanel] +user = vpanel +group = vpanel +listen = /run/php/php7.4-fpm-vpanel.sock +listen.owner = www-data +listen.group = www-data +pm = ondemand +pm.max_children = 3 +pm.process_idle_timeout = 3s +;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -fwebmaster@example.com diff --git a/etc/sudoers.d/vpanel b/etc/sudoers.d/vpanel new file mode 100644 index 0000000..4908dab --- /dev/null +++ b/etc/sudoers.d/vpanel @@ -0,0 +1,5 @@ +vpanel ALL=(root) NOPASSWD: /usr/local/bin/letsencrypt-*.sh +vpanel ALL=(root) NOPASSWD: /usr/local/bin/vhost*.sh +vpanel ALL=(Debian-exim:ssl-cert) NOPASSWD: /usr/local/bin/vmail-dkim-*.sh +vpanel ALL=(vmail) NOPASSWD: /usr/local/bin/vmail*.sh +vpanel ALL=(root) NOPASSWD: /usr/local/bin/wp-installer.sh diff --git a/panel/classes/Panel.php b/panel/classes/Panel.php new file mode 100644 index 0000000..ec80027 --- /dev/null +++ b/panel/classes/Panel.php @@ -0,0 +1,349 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +class Panel { + + function beforeRoute($f3) { + + /* check for existing session */ + if ($f3->exists('SESSION.expiration')) { + if ($f3->get('SESSION.expiration') > time()) { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + if ($f3->get('PATH') == '/Login') { + $f3->reroute('/'); + } + } else { + $f3->clear('SESSION.expiration'); + $messages[] = "Login has expired. Please re-enter login info."; + $f3->set('SESSION.messages', $messages); + if ($f3->get('PATH') != '/Login') { + $f3->reroute('/Login'); + } + } + } else { + if ($f3->get('PATH') != '/Login') { + $f3->reroute('/Login'); + } + } + + /* build subnav */ + $nav_home = preg_replace('/^\//', '', $f3->get('BASE')); + $nav_path = $f3->get('PATH'); + if ($nav_path != '/') { + $nav_parts = explode('/', $nav_path); + $nav_parts[0] = $nav_home; + $subnav = array(); + $subnav_path = ''; + foreach ($nav_parts as $k=>$v) { + $subnav_part = $v; + $subnav_path .= "/$subnav_part"; + $subnav_name = str_replace('_', ' ', $v); + $subnav[$subnav_path] = $subnav_name; + } + array_shift($subnav); + $f3->set('NAV.subnav', $subnav); + $f3->set('NAV.subnav_count', count($subnav)); + } + + $f3->set('NAV.fullpath', preg_replace('/\/$/', '', $f3->get('REALM'))); + + } + + public static function get($f3) { + echo \Template::instance()->render('home.html'); + } + + public static function validateEmail($email) { + + global $f3; + + if (preg_match("/^[0-9A-z]([-_.+]?[0-9A-z])*@[0-9A-z]([-.]?[0-9A-z])*\.[A-z]{2,24}$/", $email)) { + return true; + } else { + $messages[] = "$email is not a validly formatted email address."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + } + + } + + public static function validateDomain ($domain) { + + global $f3; + + if (preg_match('/^[0-9a-z]([-.]?[0-9a-z])*\.[a-z]{2,24}$/i', $domain)) { + return true; + } else { + $messages[] = "$domain is not a validly formatted domain name."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + } + +} + + public static function validateEmailPassword($password, $password_confirm) { + + global $f3; + + $messages = array(); + + if($password != $password_confirm) { + + $messages[] = "Passwords do not match."; + + } + + if(strlen($password) < 8) { + + $messages[] = "Password must be at least 8 characters long."; + + } + + $character_class = 0; + + if (preg_match('/[[:lower:]]/', $password)) { + + $character_class++; + + } + + if (preg_match('/[[:upper:]]/', $password)) { + + $character_class++; + + } + + if (preg_match('/[[:digit:]]/', $password)) { + + $character_class++; + + } + + if (preg_match('/[[:punct:]]/', $password)) { + + $character_class++; + + } + + if ($character_class < 3) { + + $messages[] = "Password must contain characters from at least three of the following four groups: Lower Case Letters, Uppler Case Leters, Numbers, Puncuation/Special Characters."; + + } + + if (preg_match('/^[[:punct:]]/', $password)) { + + $messages[] = "Password may not begin with Puncuation/Special Characters."; + + } + + if (!preg_match('/^[[:lower:][:upper:][:digit:][:punct:]]{8,}$/', $password)) { + + $messages[] = "Password may only contain characters from these four groups: Lower Case Letters, Uppler Case Letters, Numbers, Puncuation/Special Characters."; + + } + + if (count($messages) > 0) { + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + } else { + return true; + } + + } + + public static function validateEmailLocalpart($localpart) { + + global $f3; + + $messages = array(); + + if(strlen($localpart) > 64) { + + $messages[] = "Email account names (local-part) can not be longer than 64 characters."; + + } + + if (!preg_match('/^[[:lower:][:upper:][:digit:]\.\_\-]{1,64}$/', $localpart)) { + + $messages[] = "Email account names (local-part) can only contain letters, numbers, and the special characters . _ -"; + + } + + if (preg_match('/^[\.\_\-]/', $localpart)) { + + $messages[] = "Email account names (local-part) may not begin with a Special Character."; + + } + + if (preg_match('/[\.\_\-]$/', $localpart)) { + + $messages[] = "Email account names (local-part) may not end with a Special Character."; + + } + + if (preg_match('/[\.\_\-]{2,}/', $localpart)) { + + $messages[] = "Email account names (local-part) may not have consecutive Special Characters."; + + } + + if (count($messages) > 0) { + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + } else { + return true; + } + + } + + public static function validateEmailStatus($status) { + + global $f3; + + $messages = array(); + + if (is_int($status)) { + if ($status == 0 || $status == 1 || $status == 2) { + return true; + } + } + + $messages[] = "Invalid Status setting."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + + } + + public static function validateEmailQuota($quota) { + + global $f3; + + $messages = array(); + + if ($quota == "NULL" || (is_int($quota) && $quota > 0)) { + return true; + } + $messages[] = "Invalid Quota ($quota)."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + + } + + public static function validateEmailFiltering($filtering) { + + global $f3; + + $messages = array(); + + if ($filtering == 0 || $filtering == 1 || $filtering == 2) { + return true; + } + + $messages[] = "Invalid Filtering setting."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + + } + + public static function validateEmailRatelimit($ratelimit) { + + global $f3; + + $messages = array(); + + if ($ratelimit == "NULL" || $ratelimit > 0) { + return true; + } + $messages[] = "Invalid Rate Limit ($ratelimit)."; + if ($f3->exists('SESSION.messages')) { + $f3->set('SESSION.messages', array_merge($f3->get('SESSION.messages'), $messages)); + } else { + $f3->set('SESSION.messages', $messages); + } + return false; + + } + + public static function vGet($cmd, $return404 = TRUE) { + + global $f3; + + /* execute command */ + exec("/usr/local/bin/$cmd", $output, $result_code); + /* check for sucessful response */ + if ($result_code == 0) { + /* intialize array to return */ + $tmp_array = array(); + /* check for empty result (no data / not found) */ + if (count($output) == 0 && $return404) { + /* return 404 unless this is disabled by calling function */ + $f3->error(404); + } elseif (count($output) > 0) { + /* format array, first line contains headers / field names */ + $headers = str_getcsv($output[0]); + /* remove header row from array */ + array_shift($output); + /* cycle through each response line and convert cvs into array row */ + foreach ($output as $k=>$v) { + $cvs_array = str_getcsv($v); + $tmp_array[$k] = array(); + foreach ($cvs_array as $cvsk=>$cvsv) { + $tmp_array[$k][$headers[$cvsk]] = $cvsv; + } + } + } else { + /* empty response */ + return false; + } + return $tmp_array; + } else { + /* command resulted in error, check for & set error messages and send 500 response */ + if (count($output) > 0) { + foreach ($output as $k=>$output_message) { + $messages[] = "$output_message"; + } + } else { + $messages[] = "Unknown error."; + } + $f3->set('SESSION.messages', $messages); + $f3->error(500); + } + + } + +} diff --git a/panel/classes/Panel/Login.php b/panel/classes/Panel/Login.php new file mode 100644 index 0000000..5e910d3 --- /dev/null +++ b/panel/classes/Panel/Login.php @@ -0,0 +1,134 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel; + +class Login extends \Panel { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + } + + static function get($f3) { + + /* requests from ADMINIP are automatically authenticated */ + if ($f3->get('ADMINIP') != '' && $f3->get('ADMINIP') == $f3->get('IP')) { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + $f3->set('SESSION.access', 'admin'); + $messages[] = "You have been automatically logged in with connection from Admin IP " . $f3->get('IP'); + $f3->set('SESSION.messages', $messages); + $f3->reroute('/'); + } else { + /* all others must log in with valid username */ + echo \Template::instance()->render('login.html'); + } + + } + + function post($f3) { + + /* get vars */ + $username = $_POST['username']; + $password = $_POST['password']; + $domain = $f3->get('SESSION.domain'); + + /* detect mapping, authenticate */ + if ($f3->get('NAV.mapping') == 'vmail') { + /* enforce email address format for username */ + if (! strstr($username, '@')) { + $username = "$username@$domain"; + } + $emaildomain = $f3->get('HOST'); + $auth = new \Auth('smtp', ['host' => $emaildomain, 'port' => 25, 'scheme' => null]); + if ($auth->login($username, $password)) { + /* user passed smtp auth, check for admin access */ + if ($email_array = $f3->call('\Common\Functions::vGet', array("vmail-mboxes-get.sh -e $username -c", FALSE))) { + if ($email_array[0]['status'] == '1') { + $messages[] = "Email validated, but does not have admin privileges."; + $messages[] = "Please log in with an admin account."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); +// $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); +// $f3->set('SESSION.access', 'user'); +// $f3->reroute('/'); + } elseif ($email_array[0]['status'] == '2') { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + $f3->set('SESSION.access', 'admin'); + $f3->reroute('/'); + } else { + /* invalid email account status, should not happen as user passed smtp auth */ + $messages[] = "System error verifying email account status. Please contact support for further assistance."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } else { + /* failed to get email account info, should never happen as user passed smtp auth */ + $messages[] = "System error getting email account status. Please contact support for further assistance."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } else { + $messages[] = "Login failed, please try again."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } else { + /* not vmail, either vhost or vpanel, both use pwauth */ + unset($output); + exec("/usr/bin/echo -e '$username\\n$password\\n' | /usr/sbin/pwauth", $output, $result_code); + if ($result_code == 0) { + /* user validated, check for vpanel or vhost and futher confirm access */ + if ($f3->get('NAV.mapping') == 'vpanel') { + /* check for vpanel sudo access */ + unset($output); + exec("/usr/local/bin/vpanel-verify-access.sh -u $username", $output, $result_code); + if ($result_code == 0) { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + $f3->reroute('/'); + } else { + $messages[] = "User validated, but does not have admin privileges."; + $messages[] = "Please log in as an admin user."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } elseif ($f3->get('NAV.mapping') == 'vhost') { + /* user authenticated, check if they own requested domain */ + if ($domain_array = $f3->call('\Common\Functions::vGet', array("vhost-get.sh -u $username -d $domain -c", FALSE))) { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + $f3->reroute('/'); + } else { + /* user does not own domain, check if user is a vpanel admin */ + exec("/usr/local/bin/vpanel-verify-access.sh -u $username", $output, $result_code); + if ($result_code == 0) { + $f3->set('SESSION.expiration', time() + $f3->get('TIMEOUT')); + $f3->reroute('/'); + } else { + /* user does not own domain, and is not a vpanel admin */ + $messages[] = "User validated, but does not have admin privileges for $domain."; + $messages[] = "Please try another username."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } + } else { + $messages[] = "System error. Please confirm URL and try again."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } else { + $messages[] = "Login failed, please try again."; + $f3->set('SESSION.messages', $messages); + echo \Template::instance()->render('login.html'); + } + } + } + +} diff --git a/panel/classes/Panel/Logout.php b/panel/classes/Panel/Logout.php new file mode 100644 index 0000000..f14ecdd --- /dev/null +++ b/panel/classes/Panel/Logout.php @@ -0,0 +1,31 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel; + +class Logout extends \Panel { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + } + + static function get($f3) { + + $f3->clear('SESSION.expiration'); + $messages[] = "You have been logged out. Please re-enter info to log back in."; + $f3->set('SESSION.messages', $messages); + if ($f3->get('PATH') != '/Login') { + $f3->reroute('/Login'); + } + + } + +} diff --git a/panel/classes/Panel/Vhost.php b/panel/classes/Panel/Vhost.php new file mode 100644 index 0000000..76f7330 --- /dev/null +++ b/panel/classes/Panel/Vhost.php @@ -0,0 +1,31 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel; + +class Vhost extends \Panel { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + if ($f3->exists('SESSION.domain')) { + + /* set base path for vhost links */ + if ($f3->get('NAV.mapping') == 'vhost') { + $f3->set('NAV.vhostbase', preg_replace('/\/$/', '', $f3->get('BASE'))); + } elseif ($f3->get('NAV.mapping') == 'vpanel') { + $f3->set('NAV.vhostbase', preg_replace('/\/$/', '', $f3->get('BASE') . '/Websites/' . $f3->get('SESSION.vhost'))); + } + + } + + } + +} diff --git a/panel/classes/Panel/Vhost/Vhosts.php b/panel/classes/Panel/Vhost/Vhosts.php new file mode 100644 index 0000000..9612efe --- /dev/null +++ b/panel/classes/Panel/Vhost/Vhosts.php @@ -0,0 +1,75 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vhost; + +class Vhosts extends \Panel\Vhost { + + /* use this to make query */ + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + if ($f3->exists('PARAMS.vhost')) { + $vhost = $f3->get('PARAMS.vhost'); + if ($vhost_array = $f3->call('\Panel::vGet', array("vhost-get.sh -d $vhost -c", FALSE))) { + $f3->set('vhost_array', $vhost_array[0]); + } + } else { + if ($vhosts_array = $f3->call('\Panel::vGet', array("vhost-get.sh -c", FALSE))) { + $f3->set('vhosts_array', $vhosts_array); + } + } + + } + + static function get($f3) { + + if ($f3->exists('PARAMS.vhost')) { + + $vhost_array = $f3->get('vhost_array'); + + /* convert data for frontend display */ + if ($vhost_array['status'] == 1) { + $vhost_array['status'] = 'Enabled'; + } else { + $vhost_array['status'] = 'Disabled'; + } + if ($vhost_array['mbox_limit'] == "NULL") { + $vhost_array['mbox_limit'] = 'Unlimited'; + } + + if ($vhost_array['mbox_quota_default'] == "NULL") { + $vhost_array['mbox_quota_default'] = 'Unlimited'; + } else { + $vhost_array['mbox_quota_default'] .= ' GB'; + } + + if ($vhost_array['mbox_ratelimit_default'] == "NULL") { + $vhost_array['mbox_ratelimit_default'] = 'Unlimited'; + } else { + $vhost_array['mbox_ratelimit_default'] .= ' emails per hour'; + } + + $f3->set('vhost_array', $vhost_array); + + $vhost = $f3->get('PARAMS.vhost'); + $f3->set('page_header', "Website Hosting for $vhost"); + echo \Template::instance()->render('vhost/vhosts-vhost.html'); + + } else { + + $f3->set('page_header', "Websites"); + echo \Template::instance()->render('vhost/vhosts.html'); + + } + + } + +} diff --git a/panel/classes/Panel/Vmail.php b/panel/classes/Panel/Vmail.php new file mode 100644 index 0000000..8cc9cc8 --- /dev/null +++ b/panel/classes/Panel/Vmail.php @@ -0,0 +1,31 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel; + +class Vmail extends \Panel { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + if ($f3->exists('SESSION.domain')) { + $f3->set('PARAMS.domain', $f3->get('SESSION.domain')); + } + + /* set base path for vmail links */ + if ($f3->get('NAV.mapping') == 'vmail') { + $f3->set('NAV.vmailbase', preg_replace('/\/$/', '', $f3->get('BASE'))); + } elseif ($f3->get('NAV.mapping') == 'vpanel') { + $f3->set('NAV.vmailbase', preg_replace('/\/$/', '', $f3->get('BASE') . '/Email/' . $f3->get('PARAMS.domain'))); + } + + } + +} diff --git a/panel/classes/Panel/Vmail/Aliases.php b/panel/classes/Panel/Vmail/Aliases.php new file mode 100644 index 0000000..cc07919 --- /dev/null +++ b/panel/classes/Panel/Vmail/Aliases.php @@ -0,0 +1,63 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class Aliases extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $domain = $f3->get('PARAMS.domain'); + + /* first confirm domain exists */ + if ($f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c")) { + + /* check if we are looking for specific alias */ + if ($f3->exists('PARAMS.alias')) { + + $alias = $f3->get('PARAMS.alias'); + + /* get aliases */ + if ($aliases_array = $f3->call('\Panel::vGet', array("vmail-aliases-get.sh -a $alias@$domain -c", FALSE))) { + $f3->set('page_header', "$alias@$domain Aliases"); + $f3->set('aliases_array', $aliases_array); + } + + } elseif ($f3->exists('PARAMS.mbox')) { + + $mbox = $f3->get('PARAMS.mbox'); + + /* get aliases */ + if ($aliases_array = $f3->call('\Panel::vGet', array("vmail-aliases-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('page_header', "Aliases for $mbox@$domain"); + $f3->set('aliases_array', $aliases_array); + } + + } else { + + /* get aliases */ + if ($aliases_array = $f3->call('\Panel::vGet', array("vmail-aliases-get.sh -d $domain -c", FALSE))) { + $f3->set('page_header', "Email Aliases for $domain"); + $f3->set('aliases_array', $aliases_array); + } + + } + } + + } + + public static function get($f3) { + + echo \Template::instance()->render('vmail/aliases.html'); + + } + +} diff --git a/panel/classes/Panel/Vmail/AliasesAdd.php b/panel/classes/Panel/Vmail/AliasesAdd.php new file mode 100644 index 0000000..57c39d4 --- /dev/null +++ b/panel/classes/Panel/Vmail/AliasesAdd.php @@ -0,0 +1,67 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AliasesAdd extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* verify email account exists */ + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + $f3->call('\Panel::vGet', "vmail-mboxes-get.sh -e $mbox@$domain -c"); + + } + + static function get($f3) { + + echo \Template::instance()->render('vmail/aliases-add.html'); + + } + + function post($f3) { + + $alias = $_POST['alias']; + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + + /* validate alias local part */ + $f3->call('\Panel::validateEmailLocalpart', $alias); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\MboxesAdd::get', $f3); + } else { + exec("/usr/local/bin/vmail-aliases-add.sh -e $mbox@$domain -a $alias", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Email alias $alias@$domain added."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain/Accounts/$mbox/Aliases"); + } else { + /* failure, set error messages */ + if (count($output) > 0) { + foreach ($output as $k=>$output_message) { + $messages[] = "$output_message"; + } + } else { + $messages[] = "Unknown error."; + } + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\AliasesAdd::get', $f3); + } + } + } + +} diff --git a/panel/classes/Panel/Vmail/AliasesDelete.php b/panel/classes/Panel/Vmail/AliasesDelete.php new file mode 100644 index 0000000..233459e --- /dev/null +++ b/panel/classes/Panel/Vmail/AliasesDelete.php @@ -0,0 +1,49 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AliasesDelete extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* verify alias exists */ + $alias = $f3->get('PARAMS.alias'); + $domain = $f3->get('PARAMS.domain'); + $f3->call('\Panel::vGet', array("vmail-aliases-get.sh -a $alias -d $domain -c")); + + } + + static function get($f3) { + + $f3->set('confirm', 'true'); + echo \Template::instance()->render('vmail/aliases-delete.html'); + + } + + function post($f3) { + + /* run delete command here */ + $domain = $f3->get('PARAMS.domain'); + $alias = $f3->get('PARAMS.alias'); + $messages = array(); + exec("/usr/local/bin/vmail-aliases-del.sh -a $alias -d $domain", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Alias '$alias@$domain' has been deleted."; + } else { + $messages[] = "Error deleting alias '$alias@$domain'."; + } + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain/Aliases"); + + } + +} diff --git a/panel/classes/Panel/Vmail/AliasesEdit.php b/panel/classes/Panel/Vmail/AliasesEdit.php new file mode 100644 index 0000000..77f7234 --- /dev/null +++ b/panel/classes/Panel/Vmail/AliasesEdit.php @@ -0,0 +1,64 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AliasesEdit extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $alias = $f3->get('PARAMS.alias'); + $domain = $f3->get('PARAMS.domain'); + if ($alias_array = $f3->call('\Panel::vGet', array("vmail-aliases-get.sh -a $alias -d $domain -c", FALSE))) { + $f3->set('alias_array', $alias_array["$alias"]); + } + + } + + static public function get($f3) { + + echo \Template::instance()->render('vmail/aliases-edit.html'); + + } + + function post($f3) { + + /* run mod command here */ + $alias = $f3->get('PARAMS.alias'); + $domain = $f3->get('PARAMS.domain'); + $alias_array = $_POST; + $f3->set('alias_array', $alias_array); + extract($alias_array); + + $f3->call('\Panel::validateEmailLocalpart', array($alias)); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\ForwardsEdit::get', $f3); + } else { + exec("/usr/local/bin/vmail-aliases-mod.sh -e $alias@$domain -f $alias -k $save_local", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Forwarding for $alias@$domain updated."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain/Accounts/$alias/Forwarding"); + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\ForwardsEdit::get', $f3); + } + } + + } + +} diff --git a/panel/classes/Panel/Vmail/Autoresponders.php b/panel/classes/Panel/Vmail/Autoresponders.php new file mode 100644 index 0000000..536d484 --- /dev/null +++ b/panel/classes/Panel/Vmail/Autoresponders.php @@ -0,0 +1,112 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class Autoresponders extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $domain = $f3->get('PARAMS.domain'); + + /* first confirm domain exists */ + if ($f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c")) { + + /* check if we are looking for specific autoresponder (email account) */ + if ($f3->exists('PARAMS.mbox')) { + + $mbox = $f3->get('PARAMS.mbox'); + + /* get autoresponder */ + if ($autoresponders_array = $f3->call('\Panel::vGet', array("vmail-autoresponders-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('autoresponders_array', $autoresponders_array); + } + + } else { + + /* get autoresponders */ + if ($autoresponders_array = $f3->call('\Panel::vGet', array("vmail-autoresponders-get.sh -d $domain -c", FALSE))) { + $f3->set('autoresponders_array', $autoresponders_array); + } + + } + + } + + } + + public static function get($f3) { + + $domain = $f3->get('PARAMS.domain'); + $autoresponders_array = $f3->get('autoresponders_array'); + + /* convert data for frontend display */ + if (is_array($autoresponders_array) && count($autoresponders_array) > 0) { + + foreach($autoresponders_array as $k=>$autoresponder_array) { + + if ($autoresponder_array['status'] == 1) { + $autoresponder_array['status'] = 'Enabled'; + } else { + $autoresponder_array['status'] = 'Disabled'; + } + + $subject = $autoresponder_array['subject']; + $subject = str_replace("\\'", "'", $subject); + $subject = stripslashes($subject); + $subject = substr_replace($subject, "", 0, 1); + $subject = substr_replace($subject, "", -1); + $autoresponder_array['subject'] = $subject; + + $body = $autoresponder_array['body']; + $body = str_replace('\n', '
', $body); + $body = str_replace("\\'", "'", $body); + $body = stripslashes($body); + $body = substr_replace($body, "", 0, 1); + $body = substr_replace($body, "", -1); + $autoresponder_array['body'] = $body; + + $autoresponders_array[$k] = $autoresponder_array; + + } + + } elseif ($f3->exists('PARAMS.mbox')) { + + /* request for specific email account, autorespondering does not exist, go directly to Add autoresponder form */ + $f3->clear('autoresponders_array'); + $mbox = $f3->get('PARAMS.mbox'); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox/Autoresponder/Add"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Autoresponder/Add"); + } + + } + + if ($f3->exists('PARAMS.mbox')) { + + $mbox = $f3->get('PARAMS.mbox'); + $f3->set('page_header', "Autoresponder for $mbox@$domain"); + $f3->set('autoresponders_array', $autoresponders_array[0]); + echo \Template::instance()->render('vmail/autoresponders-autoresponder.html'); + + } else { + + $f3->set('autoresponders_array', $autoresponders_array); + $f3->set('page_header', "Email Autoresponders for $domain"); + echo \Template::instance()->render('vmail/autoresponders.html'); + + } + + } + +} diff --git a/panel/classes/Panel/Vmail/AutorespondersAdd.php b/panel/classes/Panel/Vmail/AutorespondersAdd.php new file mode 100644 index 0000000..5520c06 --- /dev/null +++ b/panel/classes/Panel/Vmail/AutorespondersAdd.php @@ -0,0 +1,67 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AutorespondersAdd extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + // verify email address exists + // verify autoresponder does *not* exist + + } + + static function get($f3) { + + echo \Template::instance()->render('vmail/autoresponders-add.html'); + + } + + function post($f3) { + + extract($_POST); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->set('autoresponder_array', $_POST); + $f3->call('\Panel\Vmail\AutorespondersAdd::get', $f3); + } else { + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + $subject = addslashes($subject); + $body = addslashes($body); + $body = str_replace('\"', '\\\\\"', $body); + $body = str_replace("\r\n", '\n', $body); + $body = str_replace("\r", '\n', $body); + $body = str_replace("\n", '\n', $body); + exec("/usr/local/bin/vmail-autoresponders-add.sh -e $mbox@$domain -u \"$subject\" -b \"$body\" -o \"$mode\" -s \"$status\"", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Autoresponder for email account $mbox@$domain added."; + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox/Autoresponder"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Autoresponder"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\AutorespondersAdd::get', $f3); + } + } + } + +} diff --git a/panel/classes/Panel/Vmail/AutorespondersDelete.php b/panel/classes/Panel/Vmail/AutorespondersDelete.php new file mode 100644 index 0000000..f90071e --- /dev/null +++ b/panel/classes/Panel/Vmail/AutorespondersDelete.php @@ -0,0 +1,50 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AutorespondersDelete extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + } + + static function get($f3) { + + $f3->set('confirm', 'true'); + echo \Template::instance()->render('vmail/autoresponders-delete.html'); + + } + + function post($f3) { + + /* run delete command here */ + $domain = $f3->get('PARAMS.domain'); + $mbox = $f3->get('PARAMS.mbox'); + $messages = array(); + exec("/usr/local/bin/vmail-autoresponders-del.sh -e $mbox@$domain", $output, $result_code); + $result_code = 0; + if ($result_code == 0) { + $messages[] = "Autoresponder for email account '$mbox@$domain' has been deleted."; + } else { + $messages[] = "Error deleting autoresponder for email account '$mbox@$domain'."; + } + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox"); + } + + } + +} diff --git a/panel/classes/Panel/Vmail/AutorespondersEdit.php b/panel/classes/Panel/Vmail/AutorespondersEdit.php new file mode 100644 index 0000000..2ce4d87 --- /dev/null +++ b/panel/classes/Panel/Vmail/AutorespondersEdit.php @@ -0,0 +1,91 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class AutorespondersEdit extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + if ($autoresponder_array = $f3->call('\Panel::vGet', array("vmail-autoresponders-get.sh -e $mbox@$domain -c", FALSE))) { + + $subject = $autoresponder_array[0]['subject']; + $subject = str_replace("\\'", "'", $subject); + $subject = stripslashes($subject); + $subject = substr_replace($subject, "", 0, 1); + $subject = substr_replace($subject, "", -1); + $autoresponder_array[0]['subject'] = $subject; + + $body = $autoresponder_array[0]['body']; + $body = str_replace('\n', "\n", $body); + $body = str_replace("\\'", "'", $body); + $body = stripslashes($body); + $body = substr_replace($body, "", 0, 1); + $body = substr_replace($body, "", -1); + $autoresponder_array[0]['body'] = $body; + + $autoresponders_array[$k] = $autoresponder_array; + + + $f3->set('autoresponder_array', $autoresponder_array[0]); + } + + } + + static public function get($f3) { + + echo \Template::instance()->render('vmail/autoresponders-edit.html'); + + } + + function post($f3) { + + /* run mod command here */ + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + $autoresponder_array = $_POST; + $f3->set('autoresponder_array', $autoresponder_array); + extract($autoresponder_array); + $subject = addslashes($subject); + $body = addslashes($body); + $body = str_replace('\"', '\\\\\"', $body); + $body = str_replace("\r\n", '\n', $body); + $body = str_replace("\r", '\n', $body); + $body = str_replace("\n", '\n', $body); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\AutorespondersEdit::get', $f3); + } else { + exec("/usr/local/bin/vmail-autoresponders-mod.sh -e $mbox@$domain -u \"$subject\" -b \"$body\" -o \"$mode\" -s \"$status\"", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Autoresponder for $mbox@$domain updated."; $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox/Autoresponder"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Autoresponder"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\AutorespondersEdit::get', $f3); + } + } + + } + +} diff --git a/panel/classes/Panel/Vmail/Domains.php b/panel/classes/Panel/Vmail/Domains.php new file mode 100644 index 0000000..d752ae3 --- /dev/null +++ b/panel/classes/Panel/Vmail/Domains.php @@ -0,0 +1,74 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class Domains extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + if ($f3->exists('PARAMS.domain')) { + $domain = $f3->get('PARAMS.domain'); + if ($domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE))) { + $f3->set('domain_array', $domain_array[0]); + } + } else { + if ($domains_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -c", FALSE))) { + $f3->set('domains_array', $domains_array); + } + } + + } + + static function get($f3) { + + if ($f3->exists('PARAMS.domain')) { + + $domain_array = $f3->get('domain_array'); + + /* convert data for frontend display */ + if ($domain_array['status'] == 1) { + $domain_array['status'] = 'Enabled'; + } else { + $domain_array['status'] = 'Disabled'; + } + if ($domain_array['mbox_limit'] == "NULL") { + $domain_array['mbox_limit'] = 'Unlimited'; + } + + if ($domain_array['mbox_quota_default'] == "NULL") { + $domain_array['mbox_quota_default'] = 'Unlimited'; + } else { + $domain_array['mbox_quota_default'] .= ' GB'; + } + + if ($domain_array['mbox_ratelimit_default'] == "NULL") { + $domain_array['mbox_ratelimit_default'] = 'Unlimited'; + } else { + $domain_array['mbox_ratelimit_default'] .= ' emails per hour'; + } + + $f3->set('domain_array', $domain_array); + + $domain = $f3->get('PARAMS.domain'); + $f3->set('page_header', "Email Domain $domain"); + echo \Template::instance()->render('vmail/domains-domain.html'); + + } else { + + $f3->set('page_header', "Email Domains"); + echo \Template::instance()->render('vmail/domains.html'); + + } + + } + +} diff --git a/panel/classes/Panel/Vmail/DomainsAdd.php b/panel/classes/Panel/Vmail/DomainsAdd.php new file mode 100644 index 0000000..55cf616 --- /dev/null +++ b/panel/classes/Panel/Vmail/DomainsAdd.php @@ -0,0 +1,125 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class DomainsAdd extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* get vm_domains defaults for "add new" form */ + $vm_domains_defaults = $f3->call('\Panel::vGet', array("vmail-defaults-get.sh -c", FALSE)); + $f3->set('vm_domains_defaults', $vm_domains_defaults[0]); + + } + + static function get($f3) { + + echo \Template::instance()->render('vmail/domains-add.html'); + + } + + function post($f3) { + + extract($_POST); + + $messages = array(); + + /* validate domain */ + if (preg_match('/^[0-9a-z]([-.]?[0-9a-z])*\.[a-z]{2,24}$/i', strtolower($_POST['domain']))) { + $domain = strtolower($_POST['domain']); + } else { + $messages[] = "Invalid domain name."; + } + + /* validate status */ + if ($_POST['status'] != 0 && $_POST['status'] != 1) { + $messages[] = "Invalid 'Status'."; + } else { + $status = $_POST['status']; + } + + /* validate mbox_limit */ + if (strtolower($_POST['mbox_limit']) == 'unlimited' || strtolower($_POST['mbox_limit'] == 'null')) { + $mbox_limit = "NULL"; + } elseif (is_numeric($_POST['mbox_limit'])) { + /* make sure mbox_limit is a possitive integer */ + $mbox_limit = abs(intval($_POST['mbox_limit'])); + if ($mbox_limit < 1) { + echo "Mailbox Limit must be a positive number or \"Unlimited\".\n"; + $messages[] = "Mailbox Limit must be a positive number or \"Unlimited\"."; + } + } else { + $messages[] = "Mailbox Limit must be a positive number or \"Unlimited\"."; + } + + /* validate mbox_quota_default */ + if (strtolower($_POST['mbox_quota_default']) == 'unlimited' || strtolower($_POST['mbox_quota_default'] == 'null')) { + $mbox_quota_default = "NULL"; + } elseif (is_numeric($_POST['mbox_quota_default'])) { + /* make sure mbox_quota_default is a possitive integer */ + $mbox_quota_default = abs(intval($_POST['mbox_quota_default'])); + if ($mbox_quota_default < 1) { + echo "Default Quota must be a positive number or \"Unlimited\".\n"; + $messages[] = "Default Quota must be a positive number or \"Unlimited\"."; + } + } else { + $messages[] = "Default Quota must be a positive number or \"Unlimited\"."; + } + + /* validate mbox_ratelimit_default */ + if (strtolower($_POST['mbox_ratelimit_default']) == 'unlimited' || strtolower($_POST['mbox_ratelimit_default'] == 'null')) { + $mbox_ratelimit_default = "NULL"; + } elseif (is_numeric($_POST['mbox_ratelimit_default'])) { + /* make sure mbox_ratelimit_default is a possitive integer */ + $mbox_ratelimit_default = abs(intval($_POST['mbox_ratelimit_default'])); + if ($mbox_ratelimit_default < 1) { + $messages[] = "Default Rate Limit must be a positive number or \"Unlimited\"."; + } + } else { + $messages[] = "Default Rate Limit must be a positive number or \"Unlimited\"."; + } + + /* check for validation errors */ + if (count($messages) > 0) { + $messages[] = "Please re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\DomainsAdd::get', $f3); + } else { + /* check if vmail domain already exists */ + $domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE)); + if (count($domain_array) == 0) { + /* add email domain */ + exec("/usr/local/bin/vmail-domains-add.sh -d $domain -l $mbox_limit -q $mbox_quota_default -r $mbox_ratelimit_default -s $status", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Email Domain $domain has been added."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain"); + } else { + if (count($output) > 0) { + foreach ($output as $k=>$output_message) { + $messages[] = "$output_message"; + } + } else { + $messages[] = "Unknown error adding Email Domain $domain."; + } + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\DomainsAdd::get', $f3); + } + } else { + $messages[] = "Email Domain '$domain' already exists."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain"); + } + } + } + +} diff --git a/panel/classes/Panel/Vmail/DomainsDelete.php b/panel/classes/Panel/Vmail/DomainsDelete.php new file mode 100644 index 0000000..0e831b4 --- /dev/null +++ b/panel/classes/Panel/Vmail/DomainsDelete.php @@ -0,0 +1,42 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class DomainsDelete extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* verify domain exists */ + $domain = $f3->get('PARAMS.domain'); + $f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c"); + + } + + static function get($f3) { + + $f3->set('confirm', 'true'); + echo \Template::instance()->render('vmail/domains-delete.html'); + + } + + function post($f3) { + + /* run delete command here */ + $domain = $f3->get('PARAMS.domain'); + exec("/usr/local/bin/vmail-domains-del.sh -d $domain", $output, $result_code); + $messages[] = "Email domain '$domain' has been deleted."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email"); + + } + +} diff --git a/panel/classes/Panel/Vmail/DomainsEdit.php b/panel/classes/Panel/Vmail/DomainsEdit.php new file mode 100644 index 0000000..3275ce0 --- /dev/null +++ b/panel/classes/Panel/Vmail/DomainsEdit.php @@ -0,0 +1,74 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class DomainsEdit extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $domain = $f3->get('PARAMS.domain'); + if ($domain_array = $f3->call('\Panel::vGet', array("vmail-domains-get.sh -d $domain -c", FALSE))) { + $f3->set('domain_array', $domain_array[0]); + } + + } + + static function get($f3) { + + $domain_array = $f3->get('domain_array'); + + /* convert data for frontend display */ + foreach ($domain_array as $k=>$v) { + + if ($domain_array['mbox_limit'] == "NULL") { + $domain_array['mbox_limit'] = 'Unlimited'; + } + + if ($domain_array['mbox_quota_default'] == "NULL") { + $domain_array['mbox_quota_default'] = 'Unlimited'; + } + + if ($domain_array['mbox_ratelimit_default'] == "NULL") { + $domain_array['mbox_ratelimit_default'] = 'Unlimited'; + } + + } + + /* reset f3 domains_array with converted data */ + $f3->set('domain_array', $domain_array); + + echo \Template::instance()->render('vmail/domains-edit.html'); + + } + + function post($f3) { + + /* run mod command here */ + $domain = $f3->get('PARAMS.domain'); + $domain_array = $_POST; + $f3->set('domain_array', $domain_array); + foreach ($domain_array as $k=>$v) { + if (strtolower($v) == 'unlimited') { + $domain_array[$k] = 'NULL'; + } + } + extract($domain_array); + + /* run domain mod command */ + exec("/usr/local/bin/vmail-domains-mod.sh -d $domain -s $status -l $mbox_limit -q $mbox_quota_default -r $mbox_ratelimit_default", $output, $result_code); + $messages[] = "Email domain '$domain' has been updated."; + $f3->set('SESSION.messages', $messages); + $f3->reroute("/Email/$domain"); + + } + +} diff --git a/panel/classes/Panel/Vmail/Forwards.php b/panel/classes/Panel/Vmail/Forwards.php new file mode 100644 index 0000000..a15eddb --- /dev/null +++ b/panel/classes/Panel/Vmail/Forwards.php @@ -0,0 +1,92 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class Forwards extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $domain = $f3->get('PARAMS.domain'); + + /* first confirm domain exists */ + if ($f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c")) { + + /* check if we are looking for specific forward (email account) */ + if ($f3->exists('PARAMS.mbox')) { + + $mbox = $f3->get('PARAMS.mbox'); + + /* get forward */ + if ($forwards_array = $f3->call('\Panel::vGet', array("vmail-forwards-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('forwards_array', $forwards_array); + } + + } else { + + /* get forwards */ + if ($forwards_array = $f3->call('\Panel::vGet', array("vmail-forwards-get.sh -d $domain -c", FALSE))) { + $f3->set('forwards_array', $forwards_array); + } + + } + } + + } + + public static function get($f3) { + + $domain = $f3->get('PARAMS.domain'); + $forwards_array = $f3->get('forwards_array'); + + /* convert data for frontend display */ + if (is_array($forwards_array) && count($forwards_array) > 0) { + + foreach($forwards_array as $k=>$forward_array) { + + if ($forward_array['save_local'] == 1) { + $forward_array['save_local'] = 'Keep'; + } else { + $forward_array['save_local'] = 'Discard'; + } + + $forwards_array[$k] = $forward_array; + + } + + $f3->set('forwards_array', $forwards_array); + + } elseif ($f3->exists('PARAMS.mbox')) { + + /* request for specific email account, forwarding does not exist, go directly to Add forwarding form */ + $f3->clear('forwards_array'); + $mbox = $f3->get('PARAMS.mbox'); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox/Forwarding/Add"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Forwarding/Add"); + } + + } + + if ($f3->exists('PARAMS.mbox')) { + $mbox = $f3->get('PARAMS.mbox'); + $f3->set('page_header', "Forwarding for $mbox@$domain"); + } else { + $f3->set('page_header', "Email Forwarding for $domain"); + } + + echo \Template::instance()->render('vmail/forwards.html'); + + } + +} diff --git a/panel/classes/Panel/Vmail/ForwardsAdd.php b/panel/classes/Panel/Vmail/ForwardsAdd.php new file mode 100644 index 0000000..8cd8c49 --- /dev/null +++ b/panel/classes/Panel/Vmail/ForwardsAdd.php @@ -0,0 +1,62 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class ForwardsAdd extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + // verify email address exists + // verify forward does *not* exist + + } + + static function get($f3) { + + echo \Template::instance()->render('vmail/forwards-add.html'); + + } + + function post($f3) { + + extract($_POST); + + $f3->call('\Panel::validateEmail', array($forward)); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->set('forward_array', $_POST); + $f3->call('\Panel\Vmail\ForwardsAdd::get', $f3); + } else { + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + exec("/usr/local/bin/vmail-forwards-add.sh -e $mbox@$domain -f $forward -k $save_local", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Forwarding for email account $mbox@$domain added."; + $f3->set('SESSION.messages', $messages); + if ($f3->get('SESSION.access') == 'vmail') { + $f3->reroute("/Accounts/$mbox/Forwarding"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Forwarding"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\ForwardsAdd::get', $f3); + } + } + } + +} diff --git a/panel/classes/Panel/Vmail/ForwardsDelete.php b/panel/classes/Panel/Vmail/ForwardsDelete.php new file mode 100644 index 0000000..c5cce65 --- /dev/null +++ b/panel/classes/Panel/Vmail/ForwardsDelete.php @@ -0,0 +1,50 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class ForwardsDelete extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + } + + static function get($f3) { + + $f3->set('confirm', 'true'); + echo \Template::instance()->render('vmail/forwards-delete.html'); + + } + + function post($f3) { + + /* run delete command here */ + $domain = $f3->get('PARAMS.domain'); + $mbox = $f3->get('PARAMS.mbox'); + $messages = array(); + exec("/usr/local/bin/vmail-forwards-del.sh -e $mbox@$domain", $output, $result_code); + $result_code = 0; + if ($result_code == 0) { + $messages[] = "Forward for email account '$mbox@$domain' has been deleted."; + } else { + $messages[] = "Error deleting forwarding for email account '$mbox@$domain'."; + } + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox"); + } + + } + +} diff --git a/panel/classes/Panel/Vmail/ForwardsEdit.php b/panel/classes/Panel/Vmail/ForwardsEdit.php new file mode 100644 index 0000000..a874621 --- /dev/null +++ b/panel/classes/Panel/Vmail/ForwardsEdit.php @@ -0,0 +1,69 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class ForwardsEdit extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + if ($forward_array = $f3->call('\Panel::vGet', array("vmail-forwards-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('forward_array', $forward_array[0]); + } + + } + + static public function get($f3) { + + echo \Template::instance()->render('vmail/forwards-edit.html'); + + } + + function post($f3) { + + /* run mod command here */ + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + $forward_array = $_POST; + $f3->set('forward_array', $forward_array); + extract($forward_array); + + $f3->call('\Panel::validateEmail', array($forward)); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\ForwardsEdit::get', $f3); + } else { + exec("/usr/local/bin/vmail-forwards-mod.sh -e $mbox@$domain -f $forward -k $save_local", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Forwarding for $mbox@$domain updated."; + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox/Forwarding"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox/Forwarding"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\ForwardsEdit::get', $f3); + } + } + + } + +} diff --git a/panel/classes/Panel/Vmail/Mboxes.php b/panel/classes/Panel/Vmail/Mboxes.php new file mode 100644 index 0000000..898305b --- /dev/null +++ b/panel/classes/Panel/Vmail/Mboxes.php @@ -0,0 +1,106 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class Mboxes extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $domain = $f3->get('PARAMS.domain'); + + /* first confirm domain exists */ + if ($f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c")) { + + /* check if we are looking for specific email account */ + if ($f3->exists('PARAMS.mbox')) { + + $mbox = $f3->get('PARAMS.mbox'); + + /* get mailbox */ + if ($mboxes_array = $f3->call('\Panel::vGet', array("vmail-mboxes-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('mboxes_array', $mboxes_array); + } + + } else { + + /* get mailboxes */ + if ($mboxes_array = $f3->call('\Panel::vGet', array("vmail-mboxes-get.sh -d $domain -c", FALSE))) { + $f3->set('mboxes_array', $mboxes_array); + } + + } + } + + } + + public static function get($f3) { + + $domain = $f3->get('PARAMS.domain'); + $mboxes_array = $f3->get('mboxes_array'); + + if (is_array($mboxes_array) && count($mboxes_array) > 0) { + + foreach ($mboxes_array as $k=>$mbox_array) { + + if ($mbox_array['status'] == 0) { + $mbox_array['status'] = 'Disabled'; + } elseif ($mbox_array['status'] == 1) { + $mbox_array['status'] = 'Enabled'; + } elseif ($mbox_array['status'] == 2) { + $mbox_array['status'] = 'Enabled - Admin'; + } + + if ($mbox_array['quota'] == "NULL") { + $mbox_array['quota'] = 'Unlimited'; + } else { + $mbox_array['quota'] .= ' GB'; + } + + if ($mbox_array['ratelimit'] == "NULL") { + $mbox_array['ratelimit'] = 'Unlimited'; + } else { + $mbox_array['ratelimit'] .= ' emails per hour'; + } + + if ($mbox_array['filter'] == 0) { + $mbox_array['filter'] = 'None'; + } elseif ($mbox_array['filter'] == 1) { + $mbox_array['filter'] = 'Junk Only'; + } elseif ($mbox_array['filter'] == 2) { + $mbox_array['filter'] = 'Junk & Spam'; + } + + $mboxes_array[$k] = $mbox_array; + + } + + } + + if ($f3->exists('PARAMS.mbox')) { + + $f3->set('mbox_array', $mboxes_array[0]); + $mbox = $f3->get('PARAMS.mbox'); + $f3->set('page_header', "Email Account $mbox@$domain Details"); + echo \Template::instance()->render('vmail/mboxes-mbox.html'); + + } else { + + $f3->set('mboxes_array', $mboxes_array); + $domain = $f3->get('PARAMS.domain'); + $f3->set('page_header', "Email Accounts for $domain"); + echo \Template::instance()->render('vmail/mboxes.html'); + + } + + } + +} diff --git a/panel/classes/Panel/Vmail/MboxesAdd.php b/panel/classes/Panel/Vmail/MboxesAdd.php new file mode 100644 index 0000000..ce4187d --- /dev/null +++ b/panel/classes/Panel/Vmail/MboxesAdd.php @@ -0,0 +1,102 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class MboxesAdd extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* get system defaults for new email accounts, also serves to verify domain exists */ + $domain = $f3->get('PARAMS.domain'); + if ($mbox_defaults = $f3->call('\Panel::vGet', "vmail-domains-get.sh -d $domain -c")) { + /* check mailbox allocation limit */ + $mbox_limit = $mbox_defaults[0]['mbox_limit']; + $mbox_allocated = $mbox_defaults[0]['mbox_allocated']; + if ($mbox_limit != 'NULL') { + if ($mbox_allocated >= $mbox_limit) { + $messages[] = "This domain has used all $mbox_limit of it's available email accounts."; + $f3->set('SESSION.messages', $messages); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts"); + } else { + $f3->reroute("/Email/$domain/Accounts"); + } + } + } else { + $mbox = array(); + $mbox['status'] = 1; + $mbox['quota'] = $mbox_defaults[0]['mbox_quota_default']; + if ($mbox['quota'] == 'NULL') { $mbox['quota'] = 'Unlimited'; } + $mbox['ratelimit'] = $mbox_defaults[0]['mbox_ratelimit_default']; + if ($mbox['ratelimit'] == 'NULL') { $mbox['ratelimit'] = 'Unlimited'; } + $mbox['filtering'] = 2; + $f3->set('mbox', $mbox); + } + } + + } + + static function get($f3) { + + echo \Template::instance()->render('vmail/mboxes-add.html'); + + } + + function post($f3) { + + extract($_POST); + + $f3->call('\Panel::validateEmailLocalpart', $localpart); + $f3->call('\Panel::validateEmailPassword', array($password, $password_confirm)); + settype($status, "integer"); + $f3->call('\Panel::validateEmailStatus', $status); + if (strtolower($quota == 'unlimited') || strtolower($quota == 'null')) { + $quota = "NULL"; + } else { + settype($quota, "integer"); + } + $f3->call('\Panel::validateEmailQuota', $quota); + if (strtolower($ratelimit == 'unlimited') || strtolower($ratelimit == 'null')) { + $ratelimit = "NULL"; + } + $f3->call('\Panel::validateEmailRatelimit', $ratelimit); + $f3->call('\Panel::validateEmailFiltering', $filtering); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->set('mbox', $_POST); + $f3->call('\Panel\Vmail\MboxesAdd::get', $f3); + } else { + $domain = $f3->get('PARAMS.domain'); + $password = escapeshellarg($password); + exec("/usr/local/bin/vmail-mboxes-add.sh -e $localpart@$domain -p $password -q $quota -r $ratelimit -s $status -j $filtering", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Email account $localpart@$domain added."; + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts"); + } else { + $f3->reroute("/Email/$domain/Accounts"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\MboxesAdd::get', $f3); + } + } + } + +} diff --git a/panel/classes/Panel/Vmail/MboxesDelete.php b/panel/classes/Panel/Vmail/MboxesDelete.php new file mode 100644 index 0000000..f982446 --- /dev/null +++ b/panel/classes/Panel/Vmail/MboxesDelete.php @@ -0,0 +1,51 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class MboxesDelete extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + /* verify domain exists */ + + } + + static function get($f3) { + + $f3->set('confirm', 'true'); + echo \Template::instance()->render('vmail/mboxes-delete.html'); + + } + + function post($f3) { + + /* run delete command here */ + $domain = $f3->get('PARAMS.domain'); + $mbox = $f3->get('PARAMS.mbox'); + $messages = array(); + exec("/usr/local/bin/vmail-mboxes-del.sh -e $mbox@$domain -x", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Email account '$mbox@$domain' has been deleted."; + } else { + $messages[] = "Error deleting email account '$mbox@$domain'."; + } + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts"); + } else { + $f3->reroute("/Email/$domain/Accounts"); + } + + } + +} diff --git a/panel/classes/Panel/Vmail/MboxesEdit.php b/panel/classes/Panel/Vmail/MboxesEdit.php new file mode 100644 index 0000000..2b4f590 --- /dev/null +++ b/panel/classes/Panel/Vmail/MboxesEdit.php @@ -0,0 +1,113 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +namespace Panel\Vmail; + +class MboxesEdit extends \Panel\Vmail { + + function beforeRoute($f3) { + + parent::beforeRoute($f3); + + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + if ($mbox_array = $f3->call('\Panel::vGet', array("vmail-mboxes-get.sh -e $mbox@$domain -c", FALSE))) { + $f3->set('mbox_array', $mbox_array[0]); + } + + } + + static public function get($f3) { + + $mbox_array = $f3->get('mbox_array'); + + /* convert data for frontend display */ + foreach ($mbox_array as $k=>$v) { + + if ($mbox_array['quota'] == "NULL") { + $mbox_array['quota'] = 'Unlimited'; + } + + if ($mbox_array['ratelimit'] == "NULL") { + $mbox_array['ratelimit'] = 'Unlimited'; + } + + } + + /* reset f3 mbox_array with converted data */ + $f3->set('mbox_array', $mbox_array); + + echo \Template::instance()->render('vmail/mboxes-edit.html'); + + } + + function post($f3) { + + /* run mod command here */ + $mbox = $f3->get('PARAMS.mbox'); + $domain = $f3->get('PARAMS.domain'); + $mbox_array = $_POST; + $f3->set('mbox_array', $mbox_array); + foreach ($mbox_array as $k=>$v) { + if (strtolower($v) == 'unlimited') { + $mbox_array[$k] = 'NULL'; + } + } + extract($mbox_array); + + if ($password != '') { + $f3->call('\Panel::validateEmailPassword', array($password, $password_confirm)); + } + settype($status, "integer"); + $f3->call('\Panel::validateEmailStatus', $status); + if (strtolower($quota == 'unlimited') || strtolower($quota == 'null')) { + $quota = "NULL"; + } else { + settype($quota, "integer"); + } + $f3->call('\Panel::validateEmailQuota', $quota); + if (strtolower($ratelimit == 'unlimited') || strtolower($ratelimit == 'null')) { + $ratelimit = "NULL"; + } + $f3->call('\Panel::validateEmailRatelimit', $ratelimit); + $f3->call('\Panel::validateEmailFiltering', $filtering); + + /* check for validation errors */ + if ($f3->exists('SESSION.messages')) { + $messages = $f3->get('SESSION.messages'); + $messages[] = "Please make changes and re-submit the form to try again."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\MboxesEdit::get', $f3); + } else { + if ($password != '') { + $password = escapeshellarg($password); + $pword_cmd = "-p $password"; + } else { + $pword_cmd = ''; + } + exec("/usr/local/bin/vmail-mboxes-mod.sh -e $mbox@$domain $pword_cmd -q $quota -r $ratelimit -s $status -j $filter", $output, $result_code); + if ($result_code == 0) { + $messages[] = "Success: Email account $mbox@$domain updated."; + $f3->set('SESSION.messages', $messages); + $mapping = $f3->get('NAV.mapping'); + if ($mapping == 'vmail') { + $f3->reroute("/Accounts/$mbox"); + } else { + $f3->reroute("/Email/$domain/Accounts/$mbox"); + } + } else { + $messages[] = "Error."; + $f3->set('SESSION.messages', $messages); + $f3->call('\Panel\Vmail\MboxesEdit::get', $f3); + } + } + + } + +} diff --git a/panel/config/config.ini b/panel/config/config.ini new file mode 100644 index 0000000..d9df759 --- /dev/null +++ b/panel/config/config.ini @@ -0,0 +1,21 @@ +; vpanel-stack +; https://git.stack-source.com/msb/vpanel-stack +; Copyright (c) 2022 Matthew Saunders Brown +; GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +[globals] +DEBUG=0 +UI=ui/ +AUTOLOAD=classes/ +TEMP=tmp/ +LOGS=tmp/logs/ +UPLOADS=tmp/uploads/ +PACKAGE=Stack-Panel +AUTHOR=Matthew Saunders Brown +LICENSE=GPL-3.0 +LICENSESEURL=https://www.gnu.org/licenses/gpl-3.0.txt +CASELESS=FALSE +CACHE=TRUE +; session lifetime in seconds +TIMEOUT=900 +ADMINIP= diff --git a/panel/config/maps-vhost.ini b/panel/config/maps-vhost.ini new file mode 100644 index 0000000..534530e --- /dev/null +++ b/panel/config/maps-vhost.ini @@ -0,0 +1,15 @@ +; vpanel-stack +; https://git.stack-source.com/msb/vpanel-stack +; Copyright (c) 2022 Matthew Saunders Brown +; GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +[maps] +; Panel (web admin) mapping +/ [sync] = Panel\Vhost\Vhosts +/Login [sync] = Panel\Login +/Logout [sync] = Panel\Logout +/Websites [sync] = Panel\Vhost\Vhosts +/Websites/Add [sync] = Panel\Vhost\VhostsAdd +/Websites/@vhost [sync] = Panel\Vhost\Vhosts +/Websites/@vhost/Edit [sync] = Panel\Vhost\VhostsEdit +/Websites/@vhost/Delete [sync] = Panel\Vhost\VhostsDelete diff --git a/panel/config/maps-vmail.ini b/panel/config/maps-vmail.ini new file mode 100644 index 0000000..b0fc495 --- /dev/null +++ b/panel/config/maps-vmail.ini @@ -0,0 +1,31 @@ +; vpanel-stack +; https://git.stack-source.com/msb/vpanel-stack +; Copyright (c) 2022 Matthew Saunders Brown +; GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +[maps] +; Panel (web admin) mapping +/ [sync] = Panel\Vmail\Domains +/Login [sync] = Panel\Login +/Logout [sync] = Panel\Logout +/Accounts [sync] = Panel\Vmail\Mboxes +/Accounts/Add [sync] = Panel\Vmail\MboxesAdd +/Accounts/@mbox [sync] = Panel\Vmail\Mboxes +/Accounts/@mbox/Edit [sync] = Panel\Vmail\MboxesEdit +/Accounts/@mbox/Delete [sync] = Panel\Vmail\MboxesDelete +/Accounts/@mbox/Aliases [sync] = Panel\Vmail\Aliases +/Accounts/@mbox/Aliases/Add [sync] = Panel\Vmail\AliasesAdd +/Accounts/@mbox/Aliases/@alias [sync] = Panel\Vmail\Aliases +/Accounts/@mbox/Aliases/@alias/Delete [sync] = Panel\Vmail\AliasesDelete +/Accounts/@mbox/Autoresponder [sync] = Panel\Vmail\Autoresponders +/Accounts/@mbox/Autoresponder/Add [sync] = Panel\Vmail\AutorespondersAdd +/Accounts/@mbox/Autoresponder/Edit [sync] = Panel\Vmail\AutorespondersEdit +/Accounts/@mbox/Autoresponder/Delete [sync] = Panel\Vmail\AutorespondersDelete +/Accounts/@mbox/Forwarding [sync] = Panel\Vmail\Forwards +/Accounts/@mbox/Forwarding/Add [sync] = Panel\Vmail\ForwardsAdd +/Accounts/@mbox/Forwarding/Edit [sync] = Panel\Vmail\ForwardsEdit +/Accounts/@mbox/Forwarding/Delete [sync] = Panel\Vmail\ForwardsDelete +/Aliases [sync] = Panel\Vmail\Aliases +/Aliases/@alias [sync] = Panel\Vmail\Aliases +/Autoresponders [sync] = Panel\Vmail\Autoresponders +/Forwards [sync] = Panel\Vmail\Forwards diff --git a/panel/config/maps-vpanel.ini b/panel/config/maps-vpanel.ini new file mode 100644 index 0000000..61106c9 --- /dev/null +++ b/panel/config/maps-vpanel.ini @@ -0,0 +1,75 @@ +; vpanel-stack +; https://git.stack-source.com/msb/vpanel-stack +; Copyright (c) 2022 Matthew Saunders Brown +; GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +[maps] +; Panel (web admin) mapping +/ [sync] = Panel +/Login [sync] = Panel\Login +/Logout [sync] = Panel\Logout +/Email [sync] = Panel\Vmail\Domains +/Email/Add [sync] = Panel\Vmail\DomainsAdd +/Email/@domain [sync] = Panel\Vmail\Domains +/Email/@domain/Edit [sync] = Panel\Vmail\DomainsEdit +/Email/@domain/Delete [sync] = Panel\Vmail\DomainsDelete +/Email/@domain/Accounts [sync] = Panel\Vmail\Mboxes +/Email/@domain/Accounts/Add [sync] = Panel\Vmail\MboxesAdd +/Email/@domain/Accounts/@mbox [sync] = Panel\Vmail\Mboxes +/Email/@domain/Accounts/@mbox/Edit [sync] = Panel\Vmail\MboxesEdit +/Email/@domain/Accounts/@mbox/Delete [sync] = Panel\Vmail\MboxesDelete +/Email/@domain/Accounts/@mbox/Aliases [sync] = Panel\Vmail\Aliases +/Email/@domain/Accounts/@mbox/Aliases/Add [sync] = Panel\Vmail\AliasesAdd +/Email/@domain/Accounts/@mbox/Aliases/@alias [sync] = Panel\Vmail\Aliases +/Email/@domain/Accounts/@mbox/Aliases/@alias/Delete [sync] = Panel\Vmail\AliasesDelete +/Email/@domain/Accounts/@mbox/Autoresponder [sync] = Panel\Vmail\Autoresponders +/Email/@domain/Accounts/@mbox/Autoresponder/Add [sync] = Panel\Vmail\AutorespondersAdd +/Email/@domain/Accounts/@mbox/Autoresponder/Edit [sync] = Panel\Vmail\AutorespondersEdit +/Email/@domain/Accounts/@mbox/Autoresponder/Delete [sync] = Panel\Vmail\AutorespondersDelete +/Email/@domain/Accounts/@mbox/Forwarding [sync] = Panel\Vmail\Forwards +/Email/@domain/Accounts/@mbox/Forwarding/Add [sync] = Panel\Vmail\ForwardsAdd +/Email/@domain/Accounts/@mbox/Forwarding/Edit [sync] = Panel\Vmail\ForwardsEdit +/Email/@domain/Accounts/@mbox/Forwarding/Delete [sync] = Panel\Vmail\ForwardsDelete +/Email/@domain/Aliases [sync] = Panel\Vmail\Aliases +/Email/@domain/Aliases/@alias [sync] = Panel\Vmail\Aliases +/Email/@domain/Autoresponders [sync] = Panel\Vmail\Autoresponders +/Email/@domain/Forwards [sync] = Panel\Vmail\Forwards + +/Websites [sync] = Panel\Vhost\Vhosts +/Websites/Add [sync] = Panel\Vhost\VhostsAdd +/Websites/@vhost [sync] = Panel\Vhost\Vhosts +/Websites/@vhost/Edit [sync] = Panel\Vhost\VhostsEdit +/Websites/@vhost/Delete [sync] = Panel\Vhost\VhostsDelete + +/Websites/@vhost/Databases [sync] = Panel\Vhost\Databases +/Websites/@vhost/Databases/Add [sync] = Panel\Vhost\DatabasesAdd +/Websites/@vhost/Databases/Delete [sync] = Panel\Vhost\DatabasesDelete +/Websites/@vhost/Databases/Users [sync] = Panel\Vhost\DatabasesUsers +/Websites/@vhost/Databases/Users/Add [sync] = Panel\Vhost\DatabasesUsersAdd +/Websites/@vhost/Databases/Users/Edit [sync] = Panel\Vhost\DatabasesUsersEdit +/Websites/@vhost/Databases/Users/Delete [sync] = Panel\Vhost\DatabasesUsersDelete + +/Websites/Redirects [sync] = Panel\Vhost\Redirects +/Websites/Redirects/Add [sync] = Panel\Vhost\RedirectsAdd +/Websites/Redirects/@redirect [sync] = Panel\Vhost\Redirects +/Websites/Redirects/@redirect/Edit [sync] = Panel\Vhost\RedirectsEdit +/Websites/Redirects/@redirect/Delete [sync] = Panel\Vhost\RedirectsDelete + +/Websites/Aliases [sync] = Panel\Vhost\Aliases +/Websites/Aliases/Add [sync] = Panel\Vhost\AliasesAdd +/Websites/Aliases/@alias [sync] = Panel\Vhost\Aliases\@alias +/Websites/Aliases/@alias/Edit [sync] = Panel\Vhost\AliasesEdit +/Websites/Aliases/@alias/Delete [sync] = Panel\Vhost\AliasesDelete + +/Websites/@vhost/Aliases [sync] = Panel\Vhost\Aliases +/Websites/@vhost/Aliases/Add [sync] = Panel\Vhost\AliasesAdd +/Websites/@vhost/Aliases/@alias [sync] = Panel\Vhost\Aliases\@alias +/Websites/@vhost/Aliases/@alias/Edit [sync] = Panel\Vhost\AliasesEdit +/Websites/@vhost/Aliases/@alias/Delete [sync] = Panel\Vhost\AliasesDelete + +; System users (ssh, websites) +/Users [sync] = Panel\Vhost\Users +/Users/Add [sync] = Panel\Vhost\UsersAdd +/Users/@user [sync] = Panel\Vhost\Users +/Users/@user/Edit [sync] = Panel\Vhost\UsersEdit +/Users/@user/Delete [sync] = Panel\Vhost\UsersDelete diff --git a/panel/index.php b/panel/index.php new file mode 100644 index 0000000..9174495 --- /dev/null +++ b/panel/index.php @@ -0,0 +1,45 @@ + + * GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + */ + +/* include framework */ +$f3 = require('f3/base.php'); +/* load f3 */ +$f3 = \Base::instance(); +/* load [globals] configuration */ +$f3->config('config/config.ini'); +/* start session, used for authentication data */ +new Session(); + +/* initialize NAV array */ +$f3->set('NAV', array()); +$f3->set('NAV.hostname', trim(`/bin/hostname -f`)); + +/* configurations based on hostname */ +if ($f3->get('HOST') == $f3->get('NAV.hostname')) { + $mapping = 'vpanel'; + $f3->clear('SESSION.domain'); +} elseif (preg_match('/^mail\./i', $f3->get('HOST'))) { + $mapping = 'vmail'; + $domain = preg_replace('/^mail\./i', '', $f3->get('HOST')); + $f3->set('SESSION.domain', $domain); +} else { + $mapping = 'vhost'; + $domain = preg_replace('/^www\./i', '', $f3->get('HOST')); + $f3->set('SESSION.domain', $domain); +} +$f3->set('NAV.mapping', $mapping); +$f3->config("config/maps-$mapping.ini"); + +/* custom error page */ +$f3->set('ONERROR',function($f3){ + echo \Template::instance()->render('error.html'); +}); + +$f3->run(); + diff --git a/panel/robots.txt b/panel/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/panel/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/panel/ui/css/awsm.css b/panel/ui/css/awsm.css new file mode 100644 index 0000000..0abdd99 --- /dev/null +++ b/panel/ui/css/awsm.css @@ -0,0 +1,700 @@ +@charset "UTF-8"; +/*! + * awsm.css v3.0.7 (https://igoradamenko.github.io/awsm.css/) + * Copyright 2015 Igor Adamenko (https://igoradamenko.com) + * Licensed under MIT (https://github.com/igoradamenko/awsm.css/blob/master/LICENSE.md) + * Modified by Matthew Saunders Brown 2022 + * and included here as part of vpanel-stack (https://git.stack-source.com/msb/vpanel-stack) + */ +html{ + font-family:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "PT Sans", "Open Sans", "Fira Sans", "Droid Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size:100%; + line-height:1.4; + background:white; + color:black; + -webkit-overflow-scrolling:touch; +} + +body{ + margin:1.2em; + font-size:1rem; +} +@media (min-width: 20rem){ + body{ + font-size:calc(1rem + 0.00625 * (100vw - 20rem)); + } +} +@media (min-width: 50rem){ + body{ + font-size:1.125rem; + } +} +body header, +body main, +body footer, +body article{ + position:relative; + max-width:50rem; + margin:0 auto; +} +body > header{ + margin-bottom:1em; +} +body > header h1{ + margin:0; + font-size:1.5em; +} +body > header p{ + margin:0; + font-size:0.85em; +} +body > footer{ + margin-top:6em; + padding-bottom:1.5em; + text-align:center; + font-size:0.8rem; + color:#aaaaaa; +} + +nav{ + margin:1em 0; +} +nav ul{ + list-style:none; + margin:0; + padding:0; +} +nav li{ + display:inline-block; + margin-right:1em; + margin-bottom:0.25em; +} +nav li:last-child{ + margin-right:0; +} +nav a:visited{ + color:#0064c1; +} +nav a:hover{ + color:#f00000; +} + +ul, ol{ + margin-top:0; + padding-top:0; + padding-left:2.5em; +} +ul li + li, ol li + li{ + margin-top:0.25em; +} +ul li > details, ol li > details{ + margin:0; +} + +p{ + margin:1em 0; + -webkit-hyphens:auto; + -ms-hyphens:auto; + hyphens:auto; +} +p:first-child{ + margin-top:0; +} +p:last-child{ + margin-bottom:0; +} +p + ul, p + ol{ + margin-top:-0.75em; +} +p img, p picture{ + float:right; + margin-bottom:0.5em; + margin-left:0.5em; +} +p picture img{ + float:none; + margin:0; +} + +dd{ + margin-bottom:1em; + margin-left:0; + padding-left:2.5em; +} + +dt{ + font-weight:700; +} + +blockquote{ + margin:0; + padding-left:2.5em; +} + +aside{ + margin:0.5em 0; + font-style:italic; + color:#aaaaaa; +} +@media (min-width: 65rem){ + aside{ + position:absolute; + right:-12.5rem; + width:9.375rem; + max-width:9.375rem; + margin:0; + padding-left:0.5em; + font-size:0.8em; + border-left:1px solid #f2f2f2; + } +} +aside:first-child{ + margin-top:0; +} +aside:last-child{ + margin-bottom:0; +} + +section + section{ + margin-top:2em; +} + +h1, h2, h3, h4, h5, h6{ + margin:1.25em 0 0; + line-height:1.2; +} +h1:hover > a[href^="#"][id]:empty, h1:focus > a[href^="#"][id]:empty, h2:hover > a[href^="#"][id]:empty, h2:focus > a[href^="#"][id]:empty, h3:hover > a[href^="#"][id]:empty, h3:focus > a[href^="#"][id]:empty, h4:hover > a[href^="#"][id]:empty, h4:focus > a[href^="#"][id]:empty, h5:hover > a[href^="#"][id]:empty, h5:focus > a[href^="#"][id]:empty, h6:hover > a[href^="#"][id]:empty, h6:focus > a[href^="#"][id]:empty{ + opacity:1; +} +h1 + p, h1 + details, h2 + p, h2 + details, h3 + p, h3 + details, h4 + p, h4 + details, h5 + p, h5 + details, h6 + p, h6 + details{ + margin-top:0.5em; +} +h1 > a[href^="#"][id]:empty, h2 > a[href^="#"][id]:empty, h3 > a[href^="#"][id]:empty, h4 > a[href^="#"][id]:empty, h5 > a[href^="#"][id]:empty, h6 > a[href^="#"][id]:empty{ + position:absolute; + left:-0.65em; + opacity:0; + text-decoration:none; + font-weight:400; + line-height:1; + color:#aaaaaa; +} +@media (min-width: 50rem){ + h1 > a[href^="#"][id]:empty, h2 > a[href^="#"][id]:empty, h3 > a[href^="#"][id]:empty, h4 > a[href^="#"][id]:empty, h5 > a[href^="#"][id]:empty, h6 > a[href^="#"][id]:empty{ + left:-0.8em; + } +} +h1 > a[href^="#"][id]:empty:target, h1 > a[href^="#"][id]:empty:hover, h1 > a[href^="#"][id]:empty:focus, h2 > a[href^="#"][id]:empty:target, h2 > a[href^="#"][id]:empty:hover, h2 > a[href^="#"][id]:empty:focus, h3 > a[href^="#"][id]:empty:target, h3 > a[href^="#"][id]:empty:hover, h3 > a[href^="#"][id]:empty:focus, h4 > a[href^="#"][id]:empty:target, h4 > a[href^="#"][id]:empty:hover, h4 > a[href^="#"][id]:empty:focus, h5 > a[href^="#"][id]:empty:target, h5 > a[href^="#"][id]:empty:hover, h5 > a[href^="#"][id]:empty:focus, h6 > a[href^="#"][id]:empty:target, h6 > a[href^="#"][id]:empty:hover, h6 > a[href^="#"][id]:empty:focus{ + opacity:1; + box-shadow:none; + color:black; +} +h1 > a[href^="#"][id]:empty:target:focus, h2 > a[href^="#"][id]:empty:target:focus, h3 > a[href^="#"][id]:empty:target:focus, h4 > a[href^="#"][id]:empty:target:focus, h5 > a[href^="#"][id]:empty:target:focus, h6 > a[href^="#"][id]:empty:target:focus{ + outline:none; +} +h1 > a[href^="#"][id]:empty::before, h2 > a[href^="#"][id]:empty::before, h3 > a[href^="#"][id]:empty::before, h4 > a[href^="#"][id]:empty::before, h5 > a[href^="#"][id]:empty::before, h6 > a[href^="#"][id]:empty::before{ + content:"ยงย "; +} + +h1{ + font-size:2.5em; +} + +h2{ + font-size:1.75em; +} + +h3{ + font-size:1.25em; +} + +h4{ + font-size:1.15em; +} + +h5{ + font-size:1em; +} + +h6{ + margin-top:1em; + font-size:1em; + color:#aaaaaa; +} + +article + article{ + margin-top:4em; +} +article header p{ + font-size:0.6em; + color:#aaaaaa; +} +article header p + h1, article header p + h2{ + margin-top:-0.25em; +} +article header h1 + p, article header h2 + p{ + margin-top:0.25em; +} +article header h1 a, article header h2 a{ + color:black; +} +article header h1 a:visited, article header h2 a:visited{ + color:#aaaaaa; +} +article header h1 a:visited:hover, article header h2 a:visited:hover{ + color:#f00000; +} +article > footer{ + margin-top:1.5em; + font-size:0.85em; +} + +a{ + color:#0064c1; +} +a:visited{ + color:#8d39d0; +} +a:hover, a:active{ + outline-width:0; +} +a:hover{ + color:#f00000; +} +a abbr{ + font-size:1em; +} + +abbr{ + margin-right:-0.075em; + text-decoration:none; + -webkit-hyphens:none; + -ms-hyphens:none; + hyphens:none; + letter-spacing:0.075em; + font-size:0.9em; +} + +img, picture{ + display:block; + max-width:100%; + margin:0 auto; +} + +audio, video{ + width:100%; + max-width:100%; +} + +figure{ + margin:1em 0 0.5em; + padding:0; +} +figure + p{ + margin-top:0.5em; +} +figure figcaption{ + opacity:0.65; + font-size:0.85em; +} + +table{ + display:inline-block; + border-spacing:0; + border-collapse:collapse; + overflow-x:auto; + max-width:100%; + text-align:left; + vertical-align:top; + background:linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 0 0, linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 100% 0; + background-attachment:scroll, scroll; + background-size:1px 100%, 1px 100%; + background-repeat:no-repeat, no-repeat; +} +table caption{ + font-size:0.9em; + background:white; +} +table td, table th{ + padding:0.35em 0.75em; + vertical-align:top; + font-size:0.9em; + border:1px solid #f2f2f2; + border-top:0; + border-left:0; +} +table td:first-child, table th:first-child{ + padding-left:0; + background-image:linear-gradient(to right, white 50%, rgba(255, 255, 255, 0) 100%); + background-size:2px 100%; + background-repeat:no-repeat; +} +table td:last-child, table th:last-child{ + padding-right:0; + border-right:0; + background-image:linear-gradient(to left, white 50%, rgba(255, 255, 255, 0) 100%); + background-position:100% 0; + background-size:2px 100%; + background-repeat:no-repeat; +} +table td:only-child, table th:only-child{ + background-image:linear-gradient(to right, white 50%, rgba(255, 255, 255, 0) 100%), linear-gradient(to left, white 50%, rgba(255, 255, 255, 0) 100%); + background-position:0 0, 100% 0; + background-size:2px 100%, 2px 100%; + background-repeat:no-repeat, no-repeat; +} +table th{ + line-height:1.2; +} + +form{ + margin-right:auto; + margin-left:auto; +} +@media (min-width: 50rem){ + form{ + max-width:80%; + } +} +form select, form label{ + display:block; +} +form label:not(:first-child){ + margin-top:1em; +} +form p label{ + display:inline; +} +form p label + label{ + margin-left:1em; +} +form legend:first-child + label{ + margin-top:0; +} +form select, form input[type], form textarea{ + margin-bottom:1em; +} +form input[type=checkbox], form input[type=radio]{ + margin-bottom:0; +} + +fieldset{ + margin:0; + padding:0.5em 1em; + border:1px solid #aaaaaa; +} + +legend{ + color:#aaaaaa; +} + +button{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:inline-block; + width:auto; + background:#f2f2f2; + color:black; + cursor:pointer; +} +button:focus{ + border:1px solid black; +} +button:not([disabled]):hover{ + border:1px solid black; +} +button:active{ + background-color:#aaaaaa; +} +button[disabled]{ + color:#aaaaaa; + cursor:not-allowed; +} + +select{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:inline-block; + width:auto; + background:#f2f2f2; + color:black; + cursor:pointer; + padding-right:1.2em; + background-position:top 55% right 0.35em; + background-size:0.5em; + background-repeat:no-repeat; + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(170, 170, 170)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E"); +} +select:focus{ + border:1px solid black; +} +select:not([disabled]):hover{ + border:1px solid black; +} +select:active{ + background-color:#aaaaaa; +} +select[disabled]{ + color:#aaaaaa; + cursor:not-allowed; +} +select:not([disabled]):focus, select:not([disabled]):hover{ + background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3Cpath fill='rgb(0, 0, 0)' fill-rule='nonzero' d='M1.5 2L3 0H0z'/%3E%3C/svg%3E"); +} + +input[type=text], input[type=password], input[type^=date], input[type=email], input[type=number], input[type=search], input[type=tel], input[type=time], input[type=month], input[type=week], input[type=url]{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:block; + width:100%; + line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2); + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; +} +input[type=text]:focus, input[type=password]:focus, input[type^=date]:focus, input[type=email]:focus, input[type=number]:focus, input[type=search]:focus, input[type=tel]:focus, input[type=time]:focus, input[type=month]:focus, input[type=week]:focus, input[type=url]:focus{ + border:1px solid black; +} +input[type=text]::-moz-placeholder, input[type=password]::-moz-placeholder, input[type^=date]::-moz-placeholder, input[type=email]::-moz-placeholder, input[type=number]::-moz-placeholder, input[type=search]::-moz-placeholder, input[type=tel]::-moz-placeholder, input[type=time]::-moz-placeholder, input[type=month]::-moz-placeholder, input[type=week]::-moz-placeholder, input[type=url]::-moz-placeholder{ + color:#aaaaaa; +} +input[type=text]::-webkit-input-placeholder, input[type=password]::-webkit-input-placeholder, input[type^=date]::-webkit-input-placeholder, input[type=email]::-webkit-input-placeholder, input[type=number]::-webkit-input-placeholder, input[type=search]::-webkit-input-placeholder, input[type=tel]::-webkit-input-placeholder, input[type=time]::-webkit-input-placeholder, input[type=month]::-webkit-input-placeholder, input[type=week]::-webkit-input-placeholder, input[type=url]::-webkit-input-placeholder{ + color:#aaaaaa; +} +input[type=text]:-ms-input-placeholder, input[type=password]:-ms-input-placeholder, input[type^=date]:-ms-input-placeholder, input[type=email]:-ms-input-placeholder, input[type=number]:-ms-input-placeholder, input[type=search]:-ms-input-placeholder, input[type=tel]:-ms-input-placeholder, input[type=time]:-ms-input-placeholder, input[type=month]:-ms-input-placeholder, input[type=week]:-ms-input-placeholder, input[type=url]:-ms-input-placeholder{ + color:#aaaaaa; +} +input[type=submit], input[type=button], input[type=reset]{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:inline-block; + width:auto; + background:#f2f2f2; + color:black; + cursor:pointer; + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; +} +input[type=submit]:focus, input[type=button]:focus, input[type=reset]:focus{ + border:1px solid black; +} +input[type=submit]:not([disabled]):hover, input[type=button]:not([disabled]):hover, input[type=reset]:not([disabled]):hover{ + border:1px solid black; +} +input[type=submit]:active, input[type=button]:active, input[type=reset]:active{ + background-color:#aaaaaa; +} +input[type=submit][disabled], input[type=button][disabled], input[type=reset][disabled]{ + color:#aaaaaa; + cursor:not-allowed; +} +input[type=color]{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:block; + width:100%; + line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2); + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + width:6em; +} +input[type=color]:focus{ + border:1px solid black; +} +input[type=color]::-moz-placeholder{ + color:#aaaaaa; +} +input[type=color]::-webkit-input-placeholder{ + color:#aaaaaa; +} +input[type=color]:-ms-input-placeholder{ + color:#aaaaaa; +} +input[type=color]:hover{ + border:1px solid black; +} +input[type=file]{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:inline-block; + width:auto; + background:#f2f2f2; + color:black; + cursor:pointer; + display:block; + width:100%; + height:auto; + padding:0.75em 0.5em; + font-size:12px; + line-height:1; +} +input[type=file]:focus{ + border:1px solid black; +} +input[type=file]:not([disabled]):hover{ + border:1px solid black; +} +input[type=file]:active{ + background-color:#aaaaaa; +} +input[type=file][disabled]{ + color:#aaaaaa; + cursor:not-allowed; +} +input[type=checkbox], input[type=radio]{ + margin:-0.2em 0.75em 0 0; + vertical-align:middle; +} + +textarea{ + outline:none; + box-sizing:border-box; + height:2em; + margin:0; + padding:calc(.25em - 1px) 0.5em; + font-family:inherit; + font-size:1em; + border:1px solid #aaaaaa; + border-radius:2px; + background:white; + color:black; + display:block; + width:100%; + line-height:calc(2em - 1px * 2 - (.25em - 1px) * 2); + -webkit-appearance:none; + -moz-appearance:none; + appearance:none; + height:4.5em; + resize:vertical; + padding-top:0.5em; + padding-bottom:0.5em; +} +textarea:focus{ + border:1px solid black; +} +textarea::-moz-placeholder{ + color:#aaaaaa; +} +textarea::-webkit-input-placeholder{ + color:#aaaaaa; +} +textarea:-ms-input-placeholder{ + color:#aaaaaa; +} + +output{ + display:block; +} + +code, kbd, var, samp{ + font-family:Consolas, "Lucida Console", Monaco, monospace; + font-style:normal; +} + +pre{ + overflow-x:auto; + font-size:0.8em; + background:linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 0 0, linear-gradient(rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.15) 100%) 100% 0; + background-attachment:scroll, scroll; + background-size:1px 100%, 1px 100%; + background-repeat:no-repeat, no-repeat; +} +pre > code{ + display:inline-block; + overflow-x:visible; + box-sizing:border-box; + min-width:100%; + border-right:3px solid white; + border-left:1px solid white; +} + +hr{ + height:1px; + margin:2em 0; + border:0; + background:#f2f2f2; +} + +details{ + margin:1em 0; +} +details[open]{ + padding-bottom:0.5em; + border-bottom:1px solid #f2f2f2; +} + +summary{ + display:inline-block; + font-weight:700; + border-bottom:1px dashed; + cursor:pointer; +} +summary::-webkit-details-marker{ + display:none; +} + +noscript{ + color:#d00000; +} + +::selection{ + background:rgba(0, 100, 193, 0.25); +} diff --git a/panel/ui/error.html b/panel/ui/error.html new file mode 100644 index 0000000..cb2e8ba --- /dev/null +++ b/panel/ui/error.html @@ -0,0 +1,3 @@ + + + diff --git a/panel/ui/favicon.ico b/panel/ui/favicon.ico new file mode 100644 index 0000000..26894ba Binary files /dev/null and b/panel/ui/favicon.ico differ diff --git a/panel/ui/footer.html b/panel/ui/footer.html new file mode 100644 index 0000000..9865361 --- /dev/null +++ b/panel/ui/footer.html @@ -0,0 +1,3 @@ + + + diff --git a/panel/ui/header.html b/panel/ui/header.html new file mode 100644 index 0000000..dfc6e8c --- /dev/null +++ b/panel/ui/header.html @@ -0,0 +1,70 @@ + + + + + + + + + + + + {{@PACKAGE}} + + +
+

{{@PACKAGE}}

+ + + +

{{@page_header}}

+
+
+
+ +

{{@ERROR.text}}

+
+ +

+

+ + {{ @panel_message | raw }}
+
+
+

+ {~ unset($_SESSION['messages']); ~} +
diff --git a/panel/ui/home.html b/panel/ui/home.html new file mode 100644 index 0000000..6e56b73 --- /dev/null +++ b/panel/ui/home.html @@ -0,0 +1,5 @@ + + +Welcome to {{@PACKAGE}}. Get cracking! + + diff --git a/panel/ui/login.html b/panel/ui/login.html new file mode 100644 index 0000000..e134871 --- /dev/null +++ b/panel/ui/login.html @@ -0,0 +1,23 @@ + + +
+
+ Log In + + + + + + + + + + +
+ Enter your email address and password. +
+ +
+
+ + diff --git a/panel/ui/vhost/vhosts-vhost.html b/panel/ui/vhost/vhosts-vhost.html new file mode 100644 index 0000000..ed4adc1 --- /dev/null +++ b/panel/ui/vhost/vhosts-vhost.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + +
DomainStatusMailbox LimitDefault QuotaDefault Rate LimitAction
{{ @vhost_array.domain }}{{ @vhost_array.status }}{{ @vhost_array.mbox_limit }}{{ @vhost_array.mbox_quota_default }}{{ @vhost_array.mbox_ratelimit_default }}Edit Delete
+ + + + + diff --git a/panel/ui/vhost/vhosts.html b/panel/ui/vhost/vhosts.html new file mode 100644 index 0000000..1fe5e7d --- /dev/null +++ b/panel/ui/vhost/vhosts.html @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + +
WebsiteUsernameConfigStatus
{{ @vhost_domain.virtualhost }}{{ @vhost_domain.username }}{{ @vhost_domain.config }}{{ @vhost_domain.status }}
+

+Select a Website name above for more details and options.
+Username is the system username that owns the website files.
+Config is the Apache server Macro Virtualhost config currently set for the site.
+Status can be used to disable a site without deleting any files or other configs.
+

+ +

+Add new Website form +

+ +
+ +
+

Add New Website

+
+ Only the domain name is required + + + + + + + Check to jail user: + + + + + + + + + + +
+ Any other form instructions go here. + +
+
+ + diff --git a/panel/ui/vmail/aliases-add.html b/panel/ui/vmail/aliases-add.html new file mode 100644 index 0000000..2995486 --- /dev/null +++ b/panel/ui/vmail/aliases-add.html @@ -0,0 +1,21 @@ + + +
+
+ Add New Email Alias for {{@PARAMS.mbox}}@{{@PARAMS.domain}} + + + + + + +
+
+ +

+Aliases are pseudo addresses, they do not actually exist as an email account. Email sent to the alias you create here will be delivered to {{@PARAMS.mbox}}@{{@PARAMS.domain}}. +

+Note that the same alias can be linked to mutliple email accounts. For example, you can have info@example.com aliased to both joe@example.com & jill@example.com. +

+ + diff --git a/panel/ui/vmail/aliases-delete.html b/panel/ui/vmail/aliases-delete.html new file mode 100644 index 0000000..b32fd17 --- /dev/null +++ b/panel/ui/vmail/aliases-delete.html @@ -0,0 +1,18 @@ + + + + +
+
+ Really Delete Alias {{ @PARAMS.alias }}@{{ @PARAMS.domain }} > {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} +
+ +
+
+
+ + Go to {{ @PARAMS.domain }} Aliases
+
+
+ + diff --git a/panel/ui/vmail/aliases-edit.html b/panel/ui/vmail/aliases-edit.html new file mode 100644 index 0000000..fa11efd --- /dev/null +++ b/panel/ui/vmail/aliases-edit.html @@ -0,0 +1,36 @@ + + + +
+
+ {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} Forwarding + + + + + + + + + + +
+
+

+Forwards To must be a valid email address.
+Local Copy can be set to Keep to Forward the email and also keep a copy in the inbox for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }}. Discard will forward all incoming emails and will not keep copies in the {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} inbox. + +

+ + diff --git a/panel/ui/vmail/aliases.html b/panel/ui/vmail/aliases.html new file mode 100644 index 0000000..bcf0dc1 --- /dev/null +++ b/panel/ui/vmail/aliases.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + +
AliasEmail AccountAction
{{ @alias_array.alias }}@{{ @alias_array.domain }} > {{ @alias_array.mbox }}@{{ @alias_array.domain }}Delete
+
+ There are no email aliases for {{@PARAMS.domain}}
+
+

+Alias is a pseudo address, it doesn't actually exist as an email account.
+Email Account is the existing email account that the alias address is delivered to.
+

+

+ + + Add new email alias form + + + To add a new alias first select an email account to alias to.
+
+
+

+ + diff --git a/panel/ui/vmail/autoresponders-add.html b/panel/ui/vmail/autoresponders-add.html new file mode 100644 index 0000000..5905008 --- /dev/null +++ b/panel/ui/vmail/autoresponders-add.html @@ -0,0 +1,51 @@ + + + +
+
+ Vacation responder for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} + + + + + + + + + + + + + + +
+
+

+Vacation mode only sends an auto-reply to the first email from each incoming address.
+Autoresponder mode sends a response to every incoming email. +

+ + diff --git a/panel/ui/vmail/autoresponders-autoresponder.html b/panel/ui/vmail/autoresponders-autoresponder.html new file mode 100644 index 0000000..c736bc5 --- /dev/null +++ b/panel/ui/vmail/autoresponders-autoresponder.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Subject:{{ @autoresponders_array.subject }}
Body:{{ @autoresponders_array.body | raw }}
Mode:{{ @autoresponders_array.mode }}
Status:{{ @autoresponders_array.status }}
Action:Edit Delete
+
+ diff --git a/panel/ui/vmail/autoresponders-delete.html b/panel/ui/vmail/autoresponders-delete.html new file mode 100644 index 0000000..b9028c5 --- /dev/null +++ b/panel/ui/vmail/autoresponders-delete.html @@ -0,0 +1,19 @@ + + + + +
+
+ Really Delete Autoresponder for Email Account {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} +
+ +
+
+
+ + Go to {{ @PARAMS.mbox }}@{{ @PARAMS.domain }}
+ Go to {{ @PARAMS.domain }} Email Accounts
+
+
+ + diff --git a/panel/ui/vmail/autoresponders-edit.html b/panel/ui/vmail/autoresponders-edit.html new file mode 100644 index 0000000..3a4ad3a --- /dev/null +++ b/panel/ui/vmail/autoresponders-edit.html @@ -0,0 +1,52 @@ + + + +
+
+ Vacation responder for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} + + + + + + + + + + + + + + +
+
+ +

+Vacation mode only sends an auto-reply to the first email from each incoming address.
+Autoresponder mode sends a response to every incoming email. +

+ + diff --git a/panel/ui/vmail/autoresponders.html b/panel/ui/vmail/autoresponders.html new file mode 100644 index 0000000..b217899 --- /dev/null +++ b/panel/ui/vmail/autoresponders.html @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + +
AccountSubjectModeStatusAction
{{ @autoresponder_array.mbox }}@{{@PARAMS.domain}}{{@autoresponder_array.subject}}{{@autoresponder_array.mode}}{{@autoresponder_array.status}}Edit Delete
+
+ +

+ There are no email autorsponders for {{@PARAMS.domain}}
+ To add a new autorsponder first select an email account.
+ + + +

+ +

+ + diff --git a/panel/ui/vmail/domains-add.html b/panel/ui/vmail/domains-add.html new file mode 100644 index 0000000..9a7b8c9 --- /dev/null +++ b/panel/ui/vmail/domains-add.html @@ -0,0 +1,41 @@ + + +
+
+ Add New Email Domain (All fields are required) + + + + + + + + + + + + + + + + + + +
+ Enter "Unlimited" for no limit on Mailbox Limit, Default Quota or Default Rate Limit. + +
+
+ +

+Status can be used to temporarily disable the email domain without deleting any settings.
+Mailbox Limit is the maximum number of email accounts for this domain.
+Default Quota is the default storage limit in GB for newly created email accounts.
+Default Rate Limit is the default hourly sending rate limit for new email accounts,
and is multiplied by 10 for the daily sending limit. +

+ + + diff --git a/panel/ui/vmail/domains-delete.html b/panel/ui/vmail/domains-delete.html new file mode 100644 index 0000000..6ae9bcc --- /dev/null +++ b/panel/ui/vmail/domains-delete.html @@ -0,0 +1,19 @@ + + + + + + +
+
+ Really Delete Email Domain {{ @PARAMS.domain }} +
+ +
+ CAUTION: This will permanently remove the email domain {{ @PARAMS.domain }} including all email accounts, aliases, etc. from this server. There is no undo after this! +
+
+ +
+ + diff --git a/panel/ui/vmail/domains-domain.html b/panel/ui/vmail/domains-domain.html new file mode 100644 index 0000000..231fe68 --- /dev/null +++ b/panel/ui/vmail/domains-domain.html @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + +
DomainStatusMailbox LimitDefault QuotaDefault Rate LimitAction
{{ @domain_array.domain }}{{ @domain_array.status }}{{ @domain_array.mbox_limit }}{{ @domain_array.mbox_quota_default }}{{ @domain_array.mbox_ratelimit_default }}Edit Delete
+ + + + + + diff --git a/panel/ui/vmail/domains-edit.html b/panel/ui/vmail/domains-edit.html new file mode 100644 index 0000000..0b0fed4 --- /dev/null +++ b/panel/ui/vmail/domains-edit.html @@ -0,0 +1,47 @@ + + + +
+
+ Edit Email Domain {{ @PARAMS.domain }} (All fields are required) + + + + + + + + + + + + + + + + +
+ Enter "Unlimited" for no limit on Mailbox Limit, Default Quota or Default Rate Limit. + +
+
+

+Status can be used to temporarily disable the email domain without deleting any settings.
+Mailbox Limit is the maximum number of email accounts for this domain.
+Default Quota is the default storage limit in GB for newly created email accounts.
+Default Rate Limit is the default hourly sending rate limit for new email accounts, and is multiplied by 10 for the daily sending limit.
+

+ + diff --git a/panel/ui/vmail/domains.html b/panel/ui/vmail/domains.html new file mode 100644 index 0000000..e08a9f5 --- /dev/null +++ b/panel/ui/vmail/domains.html @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + +
DomainEmail
{{ @vmail_domain.domain }}Accounts | Aliases | Autoresponders | Forwards
+

+ Domain can be selected for domain level settings and email account defaults.
+ Accounts are individual email addresses.
+ Aliases are pseudo addresses, they do not actually exist as an email account and are instead delivered to another existing Email Account in the same domain.
+ Autoresponders can either be an out of office style vacation messages or a generic autoresponder that sends an email in response to all incoming messages.
+ Forwards are for forwading existing email addresses to another email address, either within the same domain or to an entirely different domain. +

+
+ +

There are no email domains on this server.

+
+
+ +

+Add new email domain form +

+ + diff --git a/panel/ui/vmail/forwards-add.html b/panel/ui/vmail/forwards-add.html new file mode 100644 index 0000000..8e53d62 --- /dev/null +++ b/panel/ui/vmail/forwards-add.html @@ -0,0 +1,36 @@ + + + +
+
+ Add Forwarding for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} + + + + + + + + + + +
+
+

+Forwards To must be a valid email address.
+Local Copy can be set to Keep to Forward the email and also keep a copy in the inbox for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }}. Discard will forward all incoming emails and will not keep copies in the {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} inbox. + +

+ + diff --git a/panel/ui/vmail/forwards-delete.html b/panel/ui/vmail/forwards-delete.html new file mode 100644 index 0000000..ada79a2 --- /dev/null +++ b/panel/ui/vmail/forwards-delete.html @@ -0,0 +1,19 @@ + + + + +
+
+ Really Delete Forwarding for Email Account {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} +
+ +
+
+
+ + Go to {{ @PARAMS.mbox }}@{{ @PARAMS.domain }}
+ Go to {{ @PARAMS.domain }} Email Accounts
+
+
+ + diff --git a/panel/ui/vmail/forwards-edit.html b/panel/ui/vmail/forwards-edit.html new file mode 100644 index 0000000..fa11efd --- /dev/null +++ b/panel/ui/vmail/forwards-edit.html @@ -0,0 +1,36 @@ + + + +
+
+ {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} Forwarding + + + + + + + + + + +
+
+

+Forwards To must be a valid email address.
+Local Copy can be set to Keep to Forward the email and also keep a copy in the inbox for {{ @PARAMS.mbox }}@{{ @PARAMS.domain }}. Discard will forward all incoming emails and will not keep copies in the {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} inbox. + +

+ + diff --git a/panel/ui/vmail/forwards-forward.html b/panel/ui/vmail/forwards-forward.html new file mode 100644 index 0000000..cf6b899 --- /dev/null +++ b/panel/ui/vmail/forwards-forward.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + +
Email AccountForwards ToLocal CopyAction
{{ @forward_array.mbox }}@{{ @forward_array.domain }}{{ @forward_array.forward_to }}{{ @forward_array.save_local }}Edit Delete
+
+ diff --git a/panel/ui/vmail/forwards.html b/panel/ui/vmail/forwards.html new file mode 100644 index 0000000..c0f0df2 --- /dev/null +++ b/panel/ui/vmail/forwards.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + +
AccountForwards toLocal CopyAction
{{ @forward_array.mbox }}@{{@PARAMS.domain}}{{@forward_array.forward_to}}{{@forward_array.save_local}}Edit Delete
+
+ +

+ There are no email forwards for {{@PARAMS.domain}}
+ To add a new forward first select an email account to forward.
+ + + +

+ +

+ + diff --git a/panel/ui/vmail/mboxes-add.html b/panel/ui/vmail/mboxes-add.html new file mode 100644 index 0000000..f93c61b --- /dev/null +++ b/panel/ui/vmail/mboxes-add.html @@ -0,0 +1,105 @@ + + +
+
+ Add New @{{@PARAMS.domain}} Email Account + + + + + + + + + + + + + + + + + + + + {{ @mbox.quota }} + + + + + + + + + + + {{ @mbox.ratelimit }} + + + + + + + + + + +
+ Enter "Unlimited" for no limit on Quota or Rate Limit. +
+ +
+
+ +

+Account is the "local-part" of the email address and can contain letters, numbers, and periods.
+Password must be at least 8 characters with 12 or more being highly recommended.
+The password must contain characters from at least three of the following four groups:
+Lower Case Letters, Upper Case Letters, Numbers, Punctuation/Special Characters.
+Status can be used to temporarily disable the email account. If the account is enabled with the Admin option they can managed all email accounts.
+Quota is the storage limit in GB for the email account.
+Rate Limit is the hourly sending rate limit for the email account, and is multiplied by 10 for the daily sending limit.
+Filtering should normally be left at Junk & Spam. Junk only disables Spamassassin, and None disables additional checks like SPF, DKIM & DNS Blacklists.
+ +

+ + + diff --git a/panel/ui/vmail/mboxes-delete.html b/panel/ui/vmail/mboxes-delete.html new file mode 100644 index 0000000..c9eb7e4 --- /dev/null +++ b/panel/ui/vmail/mboxes-delete.html @@ -0,0 +1,21 @@ + + + + +
+
+ Really Delete Email Account {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} +
+ +
+ CAUTION: This will permanently remove the email account {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} and any related settings (Forwarding etc.) from this server. There is no undo after this! +
+
+
+ + Go to {{ @PARAMS.domain }}
+ Go to {{ @PARAMS.domain }} Email Accounts
+
+
+ + diff --git a/panel/ui/vmail/mboxes-edit.html b/panel/ui/vmail/mboxes-edit.html new file mode 100644 index 0000000..f8dc278 --- /dev/null +++ b/panel/ui/vmail/mboxes-edit.html @@ -0,0 +1,101 @@ + + + +
+
+ Edit Email Account {{ @PARAMS.mbox }}@{{ @PARAMS.domain }} (All fields are required) + + + + + + + + + + + + + + + + + + {{ @mbox_array.quota }} + + + + + + + + + + + {{ @mbox_array.ratelimit }} + + + + + + + + + + +
+ Enter "Unlimited" for no limit on Quota or Rate Limit. +
+ +
+
+

+Password must be at least 8 characters with 12 or more being highly recommended.
+The password must contain characters from at least three of the following four groups:
+Lower Case Letters, Upper Case Letters, Numbers, Punctuation/Special Characters.
+Status can be used to temporarily disable the email account. If the account is enabled with the Admin option they can managed all email accounts.
+Quota is the storage limit in GB for the email account.
+Rate Limit is the hourly sending rate limit for the email account, and is multiplied by 10 for the daily sending limit.
+Filtering should normally be left at Junk & Spam. Junk only disables Spamassassin, and None disables additional checks like SPF, DKIM & DNS Blacklists.
+ +

+ + diff --git a/panel/ui/vmail/mboxes-mbox.html b/panel/ui/vmail/mboxes-mbox.html new file mode 100644 index 0000000..927523f --- /dev/null +++ b/panel/ui/vmail/mboxes-mbox.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + +
AccountStatusQuotaRate LimitFilteringAction
{{ @mbox_array.mbox }}@{{ @mbox_array.domain }}{{ @mbox_array.status }}{{ @mbox_array.quota }}{{ @mbox_array.ratelimit }}{{ @mbox_array.filter }}Edit Delete
+ + + + + + diff --git a/panel/ui/vmail/mboxes.html b/panel/ui/vmail/mboxes.html new file mode 100644 index 0000000..356d85c --- /dev/null +++ b/panel/ui/vmail/mboxes.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + +
EmailOptionsAccount
{{ @mbox_array.mbox }}@{{@PARAMS.domain}}Aliases | Autoresponder | ForwardingEdit Delete
+
+ +

There are no email accounts for {{@PARAMS.domain}}

+
+
+

Add new email account form

+