mirror of
https://github.com/TotalFreedomMC/BukkitTelnet.git
synced 2025-08-06 12:33:24 +00:00
Everybody has root powers (take two)
Removed concept of root completely Reformatted code.
This commit is contained in:
parent
fb77c50bcd
commit
879ecfd279
7 changed files with 762 additions and 633 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/nbproject/private/
|
||||||
|
/build/
|
||||||
|
/dist/
|
|
@ -55,7 +55,14 @@ is divided into following sections:
|
||||||
</target>
|
</target>
|
||||||
<target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
|
<target depends="-pre-init,-init-private,-init-user,-init-project,-init-macrodef-property" name="-do-init">
|
||||||
<available file="${manifest.file}" property="manifest.available"/>
|
<available file="${manifest.file}" property="manifest.available"/>
|
||||||
<available file="${application.splash}" property="splashscreen.available"/>
|
<condition property="splashscreen.available">
|
||||||
|
<and>
|
||||||
|
<not>
|
||||||
|
<equals arg1="${application.splash}" arg2="" trim="true"/>
|
||||||
|
</not>
|
||||||
|
<available file="${application.splash}"/>
|
||||||
|
</and>
|
||||||
|
</condition>
|
||||||
<condition property="main.class.available">
|
<condition property="main.class.available">
|
||||||
<and>
|
<and>
|
||||||
<isset property="main.class"/>
|
<isset property="main.class"/>
|
||||||
|
@ -70,8 +77,14 @@ is divided into following sections:
|
||||||
<isset property="main.class.available"/>
|
<isset property="main.class.available"/>
|
||||||
</and>
|
</and>
|
||||||
</condition>
|
</condition>
|
||||||
|
<condition property="do.archive">
|
||||||
|
<not>
|
||||||
|
<istrue value="${jar.archive.disabled}"/>
|
||||||
|
</not>
|
||||||
|
</condition>
|
||||||
<condition property="do.mkdist">
|
<condition property="do.mkdist">
|
||||||
<and>
|
<and>
|
||||||
|
<isset property="do.archive"/>
|
||||||
<isset property="libs.CopyLibs.classpath"/>
|
<isset property="libs.CopyLibs.classpath"/>
|
||||||
<not>
|
<not>
|
||||||
<istrue value="${mkdist.disabled}"/>
|
<istrue value="${mkdist.disabled}"/>
|
||||||
|
@ -84,40 +97,41 @@ is divided into following sections:
|
||||||
<isset property="do.mkdist"/>
|
<isset property="do.mkdist"/>
|
||||||
</and>
|
</and>
|
||||||
</condition>
|
</condition>
|
||||||
<condition property="manifest.available+main.class+mkdist.available+splashscreen.available">
|
|
||||||
<and>
|
|
||||||
<istrue value="${manifest.available+main.class+mkdist.available}"/>
|
|
||||||
<istrue value="${splashscreen.available}"/>
|
|
||||||
</and>
|
|
||||||
</condition>
|
|
||||||
<condition property="do.archive">
|
|
||||||
<not>
|
|
||||||
<istrue value="${jar.archive.disabled}"/>
|
|
||||||
</not>
|
|
||||||
</condition>
|
|
||||||
<condition property="do.archive+manifest.available">
|
<condition property="do.archive+manifest.available">
|
||||||
<and>
|
<and>
|
||||||
<isset property="manifest.available"/>
|
<isset property="manifest.available"/>
|
||||||
<istrue value="${do.archive}"/>
|
<istrue value="${do.archive}"/>
|
||||||
</and>
|
</and>
|
||||||
</condition>
|
</condition>
|
||||||
|
<condition property="do.archive+main.class.available">
|
||||||
|
<and>
|
||||||
|
<isset property="main.class.available"/>
|
||||||
|
<istrue value="${do.archive}"/>
|
||||||
|
</and>
|
||||||
|
</condition>
|
||||||
|
<condition property="do.archive+splashscreen.available">
|
||||||
|
<and>
|
||||||
|
<isset property="splashscreen.available"/>
|
||||||
|
<istrue value="${do.archive}"/>
|
||||||
|
</and>
|
||||||
|
</condition>
|
||||||
<condition property="do.archive+manifest.available+main.class">
|
<condition property="do.archive+manifest.available+main.class">
|
||||||
<and>
|
<and>
|
||||||
<istrue value="${manifest.available+main.class}"/>
|
<istrue value="${manifest.available+main.class}"/>
|
||||||
<istrue value="${do.archive}"/>
|
<istrue value="${do.archive}"/>
|
||||||
</and>
|
</and>
|
||||||
</condition>
|
</condition>
|
||||||
<condition property="do.archive+manifest.available+main.class+mkdist.available">
|
<condition property="manifest.available-mkdist.available">
|
||||||
<and>
|
<or>
|
||||||
<istrue value="${manifest.available+main.class+mkdist.available}"/>
|
<istrue value="${manifest.available}"/>
|
||||||
<istrue value="${do.archive}"/>
|
<isset property="do.mkdist"/>
|
||||||
</and>
|
</or>
|
||||||
</condition>
|
</condition>
|
||||||
<condition property="do.archive+manifest.available+main.class+mkdist.available+splashscreen.available">
|
<condition property="manifest.available+main.class-mkdist.available">
|
||||||
<and>
|
<or>
|
||||||
<istrue value="${manifest.available+main.class+mkdist.available+splashscreen.available}"/>
|
<istrue value="${manifest.available+main.class}"/>
|
||||||
<istrue value="${do.archive}"/>
|
<isset property="do.mkdist"/>
|
||||||
</and>
|
</or>
|
||||||
</condition>
|
</condition>
|
||||||
<condition property="have.tests">
|
<condition property="have.tests">
|
||||||
<or>
|
<or>
|
||||||
|
@ -173,8 +187,17 @@ is divided into following sections:
|
||||||
<condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
|
<condition else="" property="endorsed.classpath.cmd.line.arg" value="-Xbootclasspath/p:'${toString:endorsed.classpath.path}'">
|
||||||
<length length="0" string="${endorsed.classpath}" when="greater"/>
|
<length length="0" string="${endorsed.classpath}" when="greater"/>
|
||||||
</condition>
|
</condition>
|
||||||
<property name="javac.fork" value="false"/>
|
<condition else="false" property="jdkBug6558476">
|
||||||
|
<and>
|
||||||
|
<matches pattern="1\.[56]" string="${java.specification.version}"/>
|
||||||
|
<not>
|
||||||
|
<os family="unix"/>
|
||||||
|
</not>
|
||||||
|
</and>
|
||||||
|
</condition>
|
||||||
|
<property name="javac.fork" value="${jdkBug6558476}"/>
|
||||||
<property name="jar.index" value="false"/>
|
<property name="jar.index" value="false"/>
|
||||||
|
<property name="jar.index.metainf" value="${jar.index}"/>
|
||||||
<available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
|
<available file="${meta.inf.dir}/persistence.xml" property="has.persistence.xml"/>
|
||||||
</target>
|
</target>
|
||||||
<target name="-post-init">
|
<target name="-post-init">
|
||||||
|
@ -302,7 +325,9 @@ is divided into following sections:
|
||||||
<delete>
|
<delete>
|
||||||
<files includesfile="${javac.includesfile.binary}"/>
|
<files includesfile="${javac.includesfile.binary}"/>
|
||||||
</delete>
|
</delete>
|
||||||
<delete file="${javac.includesfile.binary}"/>
|
<delete>
|
||||||
|
<fileset file="${javac.includesfile.binary}"/>
|
||||||
|
</delete>
|
||||||
</sequential>
|
</sequential>
|
||||||
</macrodef>
|
</macrodef>
|
||||||
</target>
|
</target>
|
||||||
|
@ -312,7 +337,8 @@ is divided into following sections:
|
||||||
<attribute default="${excludes}" name="excludes"/>
|
<attribute default="${excludes}" name="excludes"/>
|
||||||
<attribute default="**" name="testincludes"/>
|
<attribute default="**" name="testincludes"/>
|
||||||
<sequential>
|
<sequential>
|
||||||
<junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" showoutput="true" tempdir="${build.dir}">
|
<property name="junit.forkmode" value="perTest"/>
|
||||||
|
<junit dir="${work.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" forkmode="${junit.forkmode}" showoutput="true" tempdir="${build.dir}">
|
||||||
<batchtest todir="${build.test.results.dir}">
|
<batchtest todir="${build.test.results.dir}">
|
||||||
<fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
|
<fileset dir="${test.src.dir}" excludes="@{excludes},${excludes}" includes="@{includes}">
|
||||||
<filename name="@{testincludes}"/>
|
<filename name="@{testincludes}"/>
|
||||||
|
@ -328,11 +354,56 @@ is divided into following sections:
|
||||||
<formatter type="brief" usefile="false"/>
|
<formatter type="brief" usefile="false"/>
|
||||||
<formatter type="xml"/>
|
<formatter type="xml"/>
|
||||||
<jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
|
<jvmarg line="${endorsed.classpath.cmd.line.arg}"/>
|
||||||
|
<jvmarg value="-ea"/>
|
||||||
<jvmarg line="${run.jvmargs}"/>
|
<jvmarg line="${run.jvmargs}"/>
|
||||||
</junit>
|
</junit>
|
||||||
</sequential>
|
</sequential>
|
||||||
</macrodef>
|
</macrodef>
|
||||||
</target>
|
</target>
|
||||||
|
<target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile, -profile-init-check" name="profile-init"/>
|
||||||
|
<target name="-profile-pre-init">
|
||||||
|
<!-- Empty placeholder for easier customization. -->
|
||||||
|
<!-- You can override this target in the ../build.xml file. -->
|
||||||
|
</target>
|
||||||
|
<target name="-profile-post-init">
|
||||||
|
<!-- Empty placeholder for easier customization. -->
|
||||||
|
<!-- You can override this target in the ../build.xml file. -->
|
||||||
|
</target>
|
||||||
|
<target name="-profile-init-macrodef-profile">
|
||||||
|
<macrodef name="resolve">
|
||||||
|
<attribute name="name"/>
|
||||||
|
<attribute name="value"/>
|
||||||
|
<sequential>
|
||||||
|
<property name="@{name}" value="${env.@{value}}"/>
|
||||||
|
</sequential>
|
||||||
|
</macrodef>
|
||||||
|
<macrodef name="profile">
|
||||||
|
<attribute default="${main.class}" name="classname"/>
|
||||||
|
<element name="customize" optional="true"/>
|
||||||
|
<sequential>
|
||||||
|
<property environment="env"/>
|
||||||
|
<resolve name="profiler.current.path" value="${profiler.info.pathvar}"/>
|
||||||
|
<java classname="@{classname}" dir="${profiler.info.dir}" fork="true" jvm="${profiler.info.jvm}">
|
||||||
|
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
||||||
|
<jvmarg line="${profiler.info.jvmargs}"/>
|
||||||
|
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
||||||
|
<arg line="${application.args}"/>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
<syspropertyset>
|
||||||
|
<propertyref prefix="run-sys-prop."/>
|
||||||
|
<mapper from="run-sys-prop.*" to="*" type="glob"/>
|
||||||
|
</syspropertyset>
|
||||||
|
<customize/>
|
||||||
|
</java>
|
||||||
|
</sequential>
|
||||||
|
</macrodef>
|
||||||
|
</target>
|
||||||
|
<target depends="-profile-pre-init, init, -profile-post-init, -profile-init-macrodef-profile" name="-profile-init-check">
|
||||||
|
<fail unless="profiler.info.jvm">Must set JVM to use for profiling in profiler.info.jvm</fail>
|
||||||
|
<fail unless="profiler.info.jvmargs.agent">Must set profiler agent JVM arguments in profiler.info.jvmargs.agent</fail>
|
||||||
|
</target>
|
||||||
<target depends="-init-debug-args" name="-init-macrodef-nbjpda">
|
<target depends="-init-debug-args" name="-init-macrodef-nbjpda">
|
||||||
<macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
|
<macrodef name="nbjpdastart" uri="http://www.netbeans.org/ns/j2se-project/1">
|
||||||
<attribute default="${main.class}" name="name"/>
|
<attribute default="${main.class}" name="name"/>
|
||||||
|
@ -427,6 +498,7 @@ is divided into following sections:
|
||||||
</target>
|
</target>
|
||||||
<target name="-init-macrodef-copylibs">
|
<target name="-init-macrodef-copylibs">
|
||||||
<macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
|
<macrodef name="copylibs" uri="http://www.netbeans.org/ns/j2se-project/3">
|
||||||
|
<attribute default="${manifest.file}" name="manifest"/>
|
||||||
<element name="customize" optional="true"/>
|
<element name="customize" optional="true"/>
|
||||||
<sequential>
|
<sequential>
|
||||||
<property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
|
<property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
|
||||||
|
@ -442,7 +514,7 @@ is divided into following sections:
|
||||||
</chainedmapper>
|
</chainedmapper>
|
||||||
</pathconvert>
|
</pathconvert>
|
||||||
<taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
|
<taskdef classname="org.netbeans.modules.java.j2seproject.copylibstask.CopyLibs" classpath="${libs.CopyLibs.classpath}" name="copylibs"/>
|
||||||
<copylibs compress="${jar.compress}" index="${jar.index}" jarfile="${dist.jar}" manifest="${manifest.file}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
|
<copylibs compress="${jar.compress}" index="${jar.index}" indexMetaInf="${jar.index.metainf}" jarfile="${dist.jar}" manifest="@{manifest}" runtimeclasspath="${run.classpath.without.build.classes.dir}">
|
||||||
<fileset dir="${build.classes.dir}"/>
|
<fileset dir="${build.classes.dir}"/>
|
||||||
<manifest>
|
<manifest>
|
||||||
<attribute name="Class-Path" value="${jar.classpath}"/>
|
<attribute name="Class-Path" value="${jar.classpath}"/>
|
||||||
|
@ -571,10 +643,10 @@ is divided into following sections:
|
||||||
<!-- Empty placeholder for easier customization. -->
|
<!-- Empty placeholder for easier customization. -->
|
||||||
<!-- You can override this target in the ../build.xml file. -->
|
<!-- You can override this target in the ../build.xml file. -->
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive" name="-do-jar-without-manifest" unless="manifest.available">
|
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive" name="-do-jar-without-manifest" unless="manifest.available-mkdist.available">
|
||||||
<j2seproject1:jar/>
|
<j2seproject1:jar/>
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available" name="-do-jar-with-manifest" unless="manifest.available+main.class">
|
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available" name="-do-jar-with-manifest" unless="manifest.available+main.class-mkdist.available">
|
||||||
<j2seproject1:jar manifest="${manifest.file}"/>
|
<j2seproject1:jar manifest="${manifest.file}"/>
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available+main.class" name="-do-jar-with-mainclass" unless="manifest.available+main.class+mkdist.available">
|
<target depends="init,compile,-pre-pre-jar,-pre-jar" if="do.archive+manifest.available+main.class" name="-do-jar-with-mainclass" unless="manifest.available+main.class+mkdist.available">
|
||||||
|
@ -583,44 +655,53 @@ is divided into following sections:
|
||||||
<j2seproject1:attribute name="Main-Class" value="${main.class}"/>
|
<j2seproject1:attribute name="Main-Class" value="${main.class}"/>
|
||||||
</j2seproject1:manifest>
|
</j2seproject1:manifest>
|
||||||
</j2seproject1:jar>
|
</j2seproject1:jar>
|
||||||
<echo>To run this application from the command line without Ant, try:</echo>
|
<echo level="info">To run this application from the command line without Ant, try:</echo>
|
||||||
<property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
|
<property location="${build.classes.dir}" name="build.classes.dir.resolved"/>
|
||||||
<property location="${dist.jar}" name="dist.jar.resolved"/>
|
<property location="${dist.jar}" name="dist.jar.resolved"/>
|
||||||
<pathconvert property="run.classpath.with.dist.jar">
|
<pathconvert property="run.classpath.with.dist.jar">
|
||||||
<path path="${run.classpath}"/>
|
<path path="${run.classpath}"/>
|
||||||
<map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
|
<map from="${build.classes.dir.resolved}" to="${dist.jar.resolved}"/>
|
||||||
</pathconvert>
|
</pathconvert>
|
||||||
<echo>java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
|
<echo level="info">java -cp "${run.classpath.with.dist.jar}" ${main.class}</echo>
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-jar,-pre-jar,-init-macrodef-copylibs" if="do.archive+manifest.available+main.class+mkdist.available+splashscreen.available" name="-do-jar-with-libraries-and-splashscreen">
|
<target depends="init" if="do.archive" name="-do-jar-with-libraries-create-manifest" unless="manifest.available">
|
||||||
|
<tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
|
||||||
|
<touch file="${tmp.manifest.file}" verbose="false"/>
|
||||||
|
</target>
|
||||||
|
<target depends="init" if="do.archive+manifest.available" name="-do-jar-with-libraries-copy-manifest">
|
||||||
|
<tempfile deleteonexit="true" destdir="${build.dir}" property="tmp.manifest.file"/>
|
||||||
|
<copy file="${manifest.file}" tofile="${tmp.manifest.file}"/>
|
||||||
|
</target>
|
||||||
|
<target depends="init,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest" if="do.archive+main.class.available" name="-do-jar-with-libraries-set-main">
|
||||||
|
<manifest file="${tmp.manifest.file}" mode="update">
|
||||||
|
<attribute name="Main-Class" value="${main.class}"/>
|
||||||
|
</manifest>
|
||||||
|
</target>
|
||||||
|
<target depends="init,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest" if="do.archive+splashscreen.available" name="-do-jar-with-libraries-set-splashscreen">
|
||||||
<basename file="${application.splash}" property="splashscreen.basename"/>
|
<basename file="${application.splash}" property="splashscreen.basename"/>
|
||||||
<mkdir dir="${build.classes.dir}/META-INF"/>
|
<mkdir dir="${build.classes.dir}/META-INF"/>
|
||||||
<copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
|
<copy failonerror="false" file="${application.splash}" todir="${build.classes.dir}/META-INF"/>
|
||||||
<j2seproject3:copylibs>
|
<manifest file="${tmp.manifest.file}" mode="update">
|
||||||
<customize>
|
|
||||||
<attribute name="Main-Class" value="${main.class}"/>
|
|
||||||
<attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
|
<attribute name="SplashScreen-Image" value="META-INF/${splashscreen.basename}"/>
|
||||||
</customize>
|
</manifest>
|
||||||
</j2seproject3:copylibs>
|
|
||||||
<echo>To run this application from the command line without Ant, try:</echo>
|
|
||||||
<property location="${dist.jar}" name="dist.jar.resolved"/>
|
|
||||||
<echo>java -jar "${dist.jar.resolved}"</echo>
|
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-jar,-pre-jar,-init-macrodef-copylibs" if="do.archive+manifest.available+main.class+mkdist.available" name="-do-jar-with-libraries" unless="splashscreen.available">
|
<target depends="init,-init-macrodef-copylibs,compile,-pre-pre-jar,-pre-jar,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest,-do-jar-with-libraries-set-main,-do-jar-with-libraries-set-splashscreen" if="do.mkdist" name="-do-jar-with-libraries-pack">
|
||||||
<j2seproject3:copylibs>
|
<j2seproject3:copylibs manifest="${tmp.manifest.file}"/>
|
||||||
<customize>
|
<echo level="info">To run this application from the command line without Ant, try:</echo>
|
||||||
<attribute name="Main-Class" value="${main.class}"/>
|
|
||||||
</customize>
|
|
||||||
</j2seproject3:copylibs>
|
|
||||||
<echo>To run this application from the command line without Ant, try:</echo>
|
|
||||||
<property location="${dist.jar}" name="dist.jar.resolved"/>
|
<property location="${dist.jar}" name="dist.jar.resolved"/>
|
||||||
<echo>java -jar "${dist.jar.resolved}"</echo>
|
<echo level="info">java -jar "${dist.jar.resolved}"</echo>
|
||||||
</target>
|
</target>
|
||||||
|
<target depends="-do-jar-with-libraries-pack" if="do.archive" name="-do-jar-with-libraries-delete-manifest">
|
||||||
|
<delete>
|
||||||
|
<fileset file="${tmp.manifest.file}"/>
|
||||||
|
</delete>
|
||||||
|
</target>
|
||||||
|
<target depends="init,compile,-pre-pre-jar,-pre-jar,-do-jar-with-libraries-create-manifest,-do-jar-with-libraries-copy-manifest,-do-jar-with-libraries-set-main,-do-jar-with-libraries-set-splashscreen,-do-jar-with-libraries-pack,-do-jar-with-libraries-delete-manifest" name="-do-jar-with-libraries"/>
|
||||||
<target name="-post-jar">
|
<target name="-post-jar">
|
||||||
<!-- Empty placeholder for easier customization. -->
|
<!-- Empty placeholder for easier customization. -->
|
||||||
<!-- You can override this target in the ../build.xml file. -->
|
<!-- You can override this target in the ../build.xml file. -->
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries-and-splashscreen,-do-jar-with-libraries,-post-jar" description="Build JAR." name="jar"/>
|
<target depends="init,compile,-pre-jar,-do-jar-with-manifest,-do-jar-without-manifest,-do-jar-with-mainclass,-do-jar-with-libraries,-post-jar" description="Build JAR." name="jar"/>
|
||||||
<!--
|
<!--
|
||||||
=================
|
=================
|
||||||
EXECUTION SECTION
|
EXECUTION SECTION
|
||||||
|
@ -685,6 +766,72 @@ is divided into following sections:
|
||||||
<j2seproject1:nbjpdareload/>
|
<j2seproject1:nbjpdareload/>
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
|
<target depends="init,-pre-debug-fix,-do-debug-fix" if="netbeans.home" name="debug-fix"/>
|
||||||
|
<!--
|
||||||
|
=================
|
||||||
|
PROFILING SECTION
|
||||||
|
=================
|
||||||
|
-->
|
||||||
|
<target depends="profile-init,compile" description="Profile a project in the IDE." if="netbeans.home" name="profile">
|
||||||
|
<nbprofiledirect>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
</nbprofiledirect>
|
||||||
|
<profile/>
|
||||||
|
</target>
|
||||||
|
<target depends="profile-init,compile-single" description="Profile a selected class in the IDE." if="netbeans.home" name="profile-single">
|
||||||
|
<fail unless="profile.class">Must select one file in the IDE or set profile.class</fail>
|
||||||
|
<nbprofiledirect>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
</nbprofiledirect>
|
||||||
|
<profile classname="${profile.class}"/>
|
||||||
|
</target>
|
||||||
|
<!--
|
||||||
|
=========================
|
||||||
|
APPLET PROFILING SECTION
|
||||||
|
=========================
|
||||||
|
-->
|
||||||
|
<target depends="profile-init,compile-single" if="netbeans.home" name="profile-applet">
|
||||||
|
<nbprofiledirect>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
</nbprofiledirect>
|
||||||
|
<profile classname="sun.applet.AppletViewer">
|
||||||
|
<customize>
|
||||||
|
<arg value="${applet.url}"/>
|
||||||
|
</customize>
|
||||||
|
</profile>
|
||||||
|
</target>
|
||||||
|
<!--
|
||||||
|
=========================
|
||||||
|
TESTS PROFILING SECTION
|
||||||
|
=========================
|
||||||
|
-->
|
||||||
|
<target depends="profile-init,compile-test-single" if="netbeans.home" name="profile-test-single">
|
||||||
|
<nbprofiledirect>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.test.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
</nbprofiledirect>
|
||||||
|
<junit dir="${profiler.info.dir}" errorproperty="tests.failed" failureproperty="tests.failed" fork="true" jvm="${profiler.info.jvm}" showoutput="true">
|
||||||
|
<env key="${profiler.info.pathvar}" path="${profiler.info.agentpath}:${profiler.current.path}"/>
|
||||||
|
<jvmarg value="${profiler.info.jvmargs.agent}"/>
|
||||||
|
<jvmarg line="${profiler.info.jvmargs}"/>
|
||||||
|
<test name="${profile.class}"/>
|
||||||
|
<classpath>
|
||||||
|
<path path="${run.test.classpath}"/>
|
||||||
|
</classpath>
|
||||||
|
<syspropertyset>
|
||||||
|
<propertyref prefix="test-sys-prop."/>
|
||||||
|
<mapper from="test-sys-prop.*" to="*" type="glob"/>
|
||||||
|
</syspropertyset>
|
||||||
|
<formatter type="brief" usefile="false"/>
|
||||||
|
<formatter type="xml"/>
|
||||||
|
</junit>
|
||||||
|
</target>
|
||||||
<!--
|
<!--
|
||||||
===============
|
===============
|
||||||
JAVADOC SECTION
|
JAVADOC SECTION
|
||||||
|
@ -696,11 +843,12 @@ is divided into following sections:
|
||||||
<classpath>
|
<classpath>
|
||||||
<path path="${javac.classpath}"/>
|
<path path="${javac.classpath}"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
<fileset dir="${src.dir}" excludes="${excludes}" includes="${includes}">
|
<fileset dir="${src.dir}" excludes="*.java,${excludes}" includes="${includes}">
|
||||||
<filename name="**/*.java"/>
|
<filename name="**/*.java"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
<fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
|
<fileset dir="${build.generated.sources.dir}" erroronmissingdir="false">
|
||||||
<include name="**/*.java"/>
|
<include name="**/*.java"/>
|
||||||
|
<exclude name="*.java"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
</javadoc>
|
</javadoc>
|
||||||
<copy todir="${dist.javadoc.dir}">
|
<copy todir="${dist.javadoc.dir}">
|
||||||
|
@ -731,7 +879,7 @@ is divided into following sections:
|
||||||
<target if="do.depend.true" name="-compile-test-depend">
|
<target if="do.depend.true" name="-compile-test-depend">
|
||||||
<j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
|
<j2seproject3:depend classpath="${javac.test.classpath}" destdir="${build.test.classes.dir}" srcdir="${test.src.dir}"/>
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
|
<target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test,-compile-test-depend" if="have.tests" name="-do-compile-test">
|
||||||
<j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/>
|
<j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" processorpath="${javac.test.processorpath}" srcdir="${test.src.dir}"/>
|
||||||
<copy todir="${build.test.classes.dir}">
|
<copy todir="${build.test.classes.dir}">
|
||||||
<fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
|
<fileset dir="${test.src.dir}" excludes="${build.classes.excludes},${excludes}" includes="${includes}"/>
|
||||||
|
@ -746,7 +894,7 @@ is divided into following sections:
|
||||||
<!-- Empty placeholder for easier customization. -->
|
<!-- Empty placeholder for easier customization. -->
|
||||||
<!-- You can override this target in the ../build.xml file. -->
|
<!-- You can override this target in the ../build.xml file. -->
|
||||||
</target>
|
</target>
|
||||||
<target depends="init,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
|
<target depends="init,deps-jar,compile,-pre-pre-compile-test,-pre-compile-test-single" if="have.tests" name="-do-compile-test-single">
|
||||||
<fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
|
<fail unless="javac.includes">Must select some files in the IDE or set javac.includes</fail>
|
||||||
<j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
|
<j2seproject3:force-recompile destdir="${build.test.classes.dir}"/>
|
||||||
<j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
|
<j2seproject3:javac apgeneratedsrcdir="${build.test.classes.dir}" classpath="${javac.test.classpath}" debug="true" destdir="${build.test.classes.dir}" excludes="" includes="${javac.includes}" processorpath="${javac.test.processorpath}" sourcepath="${test.src.dir}" srcdir="${test.src.dir}"/>
|
||||||
|
|
|
@ -4,5 +4,5 @@ build.xml.stylesheet.CRC32=28e38971@1.38.3.45
|
||||||
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
|
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
|
||||||
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
|
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
|
||||||
nbproject/build-impl.xml.data.CRC32=fc2d0ea5
|
nbproject/build-impl.xml.data.CRC32=fc2d0ea5
|
||||||
nbproject/build-impl.xml.script.CRC32=66049b1e
|
nbproject/build-impl.xml.script.CRC32=c47fa280
|
||||||
nbproject/build-impl.xml.stylesheet.CRC32=229523de@1.38.3.45
|
nbproject/build-impl.xml.stylesheet.CRC32=0ae3a408@1.44.1.45
|
||||||
|
|
|
@ -3,4 +3,4 @@ do.depend=false
|
||||||
do.jar=true
|
do.jar=true
|
||||||
javac.debug=true
|
javac.debug=true
|
||||||
javadoc.preview=true
|
javadoc.preview=true
|
||||||
user.properties.file=C:\\Users\\Administrator\\.netbeans\\6.9\\build.properties
|
user.properties.file=C:\\Users\\Michael\\.netbeans\\7.0\\build.properties
|
||||||
|
|
|
@ -26,11 +26,11 @@ dist.jar=${dist.dir}/MCTelnet.jar
|
||||||
dist.javadoc.dir=${dist.dir}/javadoc
|
dist.javadoc.dir=${dist.dir}/javadoc
|
||||||
endorsed.classpath=
|
endorsed.classpath=
|
||||||
excludes=
|
excludes=
|
||||||
file.reference.craftbukkit.jar=C:\\Users\\Administrator\\Documents\\NetBeansProjects\\CBJars\\craftbukkit.jar
|
file.reference.craftbukkit-0.0.1-SNAPSHOT.jar=C:\\github\\craftbukkit-0.0.1-SNAPSHOT.jar
|
||||||
includes=**
|
includes=**
|
||||||
jar.compress=false
|
jar.compress=false
|
||||||
javac.classpath=\
|
javac.classpath=\
|
||||||
${file.reference.craftbukkit.jar}
|
${file.reference.craftbukkit-0.0.1-SNAPSHOT.jar}
|
||||||
# Space-separated list of extra javac options
|
# Space-separated list of extra javac options
|
||||||
javac.compilerargs=
|
javac.compilerargs=
|
||||||
javac.deprecation=false
|
javac.deprecation=false
|
||||||
|
@ -57,6 +57,7 @@ javadoc.use=true
|
||||||
javadoc.version=false
|
javadoc.version=false
|
||||||
javadoc.windowtitle=
|
javadoc.windowtitle=
|
||||||
meta.inf.dir=${src.dir}/META-INF
|
meta.inf.dir=${src.dir}/META-INF
|
||||||
|
mkdist.disabled=false
|
||||||
platform.active=default_platform
|
platform.active=default_platform
|
||||||
run.classpath=\
|
run.classpath=\
|
||||||
${javac.classpath}:\
|
${javac.classpath}:\
|
||||||
|
|
|
@ -8,7 +8,6 @@ import java.util.ArrayList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import net.minecraft.server.MinecraftServer;
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.command.ConsoleCommandSender;
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
|
@ -22,16 +21,8 @@ import java.util.Iterator;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import org.bukkit.util.config.ConfigurationNode;
|
import org.bukkit.util.config.ConfigurationNode;
|
||||||
|
|
||||||
/*
|
public class MCTelnet extends JavaPlugin
|
||||||
* To change this template, choose Tools | Templates
|
{
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Administrator
|
|
||||||
*/
|
|
||||||
public class MCTelnet extends JavaPlugin {
|
|
||||||
private ServerSocket listenerSocket;
|
private ServerSocket listenerSocket;
|
||||||
private ArrayList<TelnetListener> clientHolder;
|
private ArrayList<TelnetListener> clientHolder;
|
||||||
private Thread listenerThread;
|
private Thread listenerThread;
|
||||||
|
@ -41,50 +32,59 @@ public class MCTelnet extends JavaPlugin {
|
||||||
|
|
||||||
public MCTelnet()
|
public MCTelnet()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//public MCTelnet(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) {
|
|
||||||
// super(pluginLoader, instance, desc, folder, plugin, cLoader);
|
|
||||||
//}
|
|
||||||
|
|
||||||
public void onDisable()
|
public void onDisable()
|
||||||
{
|
{
|
||||||
run = false;
|
run = false;
|
||||||
if (listenerSocket != null)
|
if (listenerSocket != null)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
synchronized (listenerSocket)
|
synchronized (listenerSocket)
|
||||||
{
|
{
|
||||||
if (listenerSocket != null)
|
if (listenerSocket != null)
|
||||||
|
{
|
||||||
listenerSocket.close();
|
listenerSocket.close();
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
} catch (InterruptedException ex) {
|
}
|
||||||
|
catch (InterruptedException ex)
|
||||||
|
{
|
||||||
Logger.getLogger(MCTelnet.class.getName()).log(Level.SEVERE, null, ex);
|
Logger.getLogger(MCTelnet.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onEnable() {
|
public void onEnable()
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] - Starting Up! Version: " + this.getDescription().getVersion() + " by bekvon");
|
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] - Starting Up! Version: " + this.getDescription().getVersion() + " by bekvon");
|
||||||
run = true;
|
run = true;
|
||||||
this.getConfiguration().load();
|
this.getConfiguration().load();
|
||||||
testConfig();
|
testConfig();
|
||||||
if (this.getConfiguration().getBoolean("encryptPasswords", false))
|
if (this.getConfiguration().getBoolean("encryptPasswords", false))
|
||||||
|
{
|
||||||
encryptPasswords();
|
encryptPasswords();
|
||||||
|
}
|
||||||
port = this.getConfiguration().getInt("telnetPort", port);
|
port = this.getConfiguration().getInt("telnetPort", port);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String address = this.getConfiguration().getString("listenAddress", null);
|
String address = this.getConfiguration().getString("listenAddress", null);
|
||||||
if (address != null)
|
if (address != null)
|
||||||
|
{
|
||||||
listenAddress = InetAddress.getByName(address);
|
listenAddress = InetAddress.getByName(address);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
System.out.println("[MCTelnet] Exception when trying to binding to custom address:" + ex.getMessage());
|
System.out.println("[MCTelnet] Exception when trying to binding to custom address:" + ex.getMessage());
|
||||||
|
@ -98,8 +98,10 @@ public class MCTelnet extends JavaPlugin {
|
||||||
listenerSocket = new java.net.ServerSocket(port, 10);
|
listenerSocket = new java.net.ServerSocket(port, 10);
|
||||||
}
|
}
|
||||||
clientHolder = new ArrayList<TelnetListener>();
|
clientHolder = new ArrayList<TelnetListener>();
|
||||||
listenerThread = new Thread(new Runnable() {
|
listenerThread = new Thread(new Runnable()
|
||||||
public void run() {
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
acceptConnections();
|
acceptConnections();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -107,7 +109,9 @@ public class MCTelnet extends JavaPlugin {
|
||||||
Field cfield = CraftServer.class.getDeclaredField("console");
|
Field cfield = CraftServer.class.getDeclaredField("console");
|
||||||
cfield.setAccessible(true);
|
cfield.setAccessible(true);
|
||||||
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] - Listening on: " + listenerSocket.getInetAddress().getHostAddress() + ":" + port);
|
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] - Listening on: " + listenerSocket.getInetAddress().getHostAddress() + ":" + port);
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Logger.getLogger("Minecraft").log(Level.SEVERE, "[MCTelnet] - Unable to Enable! Error: " + ex.getMessage());
|
Logger.getLogger("Minecraft").log(Level.SEVERE, "[MCTelnet] - Unable to Enable! Error: " + ex.getMessage());
|
||||||
this.setEnabled(false);
|
this.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
@ -115,13 +119,6 @@ public class MCTelnet extends JavaPlugin {
|
||||||
|
|
||||||
private void encryptPasswords()
|
private void encryptPasswords()
|
||||||
{
|
{
|
||||||
boolean isEncrypt = this.getConfiguration().getBoolean("rootEncrypted",false);
|
|
||||||
if(!isEncrypt)
|
|
||||||
{
|
|
||||||
this.getConfiguration().setProperty("rootPass", hashPassword(this.getConfiguration().getString("rootPass")));
|
|
||||||
this.getConfiguration().setProperty("rootEncrypted", true);
|
|
||||||
this.getConfiguration().save();
|
|
||||||
}
|
|
||||||
Map<String, ConfigurationNode> users = this.getConfiguration().getNodes("users");
|
Map<String, ConfigurationNode> users = this.getConfiguration().getNodes("users");
|
||||||
if (users != null)
|
if (users != null)
|
||||||
{
|
{
|
||||||
|
@ -146,14 +143,18 @@ public class MCTelnet extends JavaPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String hashPassword(String password) {
|
public static String hashPassword(String password)
|
||||||
|
{
|
||||||
String hashword = null;
|
String hashword = null;
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
MessageDigest md5 = MessageDigest.getInstance("MD5");
|
||||||
md5.update(password.getBytes());
|
md5.update(password.getBytes());
|
||||||
BigInteger hash = new BigInteger(1, md5.digest());
|
BigInteger hash = new BigInteger(1, md5.digest());
|
||||||
hashword = hash.toString(16);
|
hashword = hash.toString(16);
|
||||||
} catch (NoSuchAlgorithmException nsae) {
|
}
|
||||||
|
catch (NoSuchAlgorithmException nsae)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
return hashword;
|
return hashword;
|
||||||
}
|
}
|
||||||
|
@ -162,7 +163,8 @@ public class MCTelnet extends JavaPlugin {
|
||||||
{
|
{
|
||||||
while (run)
|
while (run)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
Socket client = listenerSocket.accept();
|
Socket client = listenerSocket.accept();
|
||||||
if (client != null)
|
if (client != null)
|
||||||
{
|
{
|
||||||
|
@ -173,9 +175,13 @@ public class MCTelnet extends JavaPlugin {
|
||||||
{
|
{
|
||||||
TelnetListener thisListener = clientHolder.get(i);
|
TelnetListener thisListener = clientHolder.get(i);
|
||||||
if (thisListener.isAlive() == false)
|
if (thisListener.isAlive() == false)
|
||||||
|
{
|
||||||
clientHolder.remove(i);
|
clientHolder.remove(i);
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
run = false;
|
run = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,29 +199,21 @@ public class MCTelnet extends JavaPlugin {
|
||||||
|
|
||||||
private void testConfig()
|
private void testConfig()
|
||||||
{
|
{
|
||||||
String testConfig = this.getConfiguration().getString("telnetPort");
|
String testConfig = this.getConfiguration().getString("telnetPort", null);
|
||||||
if (testConfig == null || testConfig.equals("")) {
|
if (testConfig == null || testConfig.equals(""))
|
||||||
|
{
|
||||||
this.getConfiguration().setProperty("telnetPort", 8765);
|
this.getConfiguration().setProperty("telnetPort", 8765);
|
||||||
this.getConfiguration().save();
|
this.getConfiguration().save();
|
||||||
}
|
}
|
||||||
testConfig = this.getConfiguration().getString("listenAddress");
|
testConfig = this.getConfiguration().getString("listenAddress", null);
|
||||||
if (testConfig == null || testConfig.equals("")) {
|
if (testConfig == null || testConfig.equals(""))
|
||||||
|
{
|
||||||
this.getConfiguration().setProperty("listenAddress", "0.0.0.0");
|
this.getConfiguration().setProperty("listenAddress", "0.0.0.0");
|
||||||
this.getConfiguration().save();
|
this.getConfiguration().save();
|
||||||
}
|
}
|
||||||
testConfig = this.getConfiguration().getString("rootPass");
|
testConfig = this.getConfiguration().getString("encryptPasswords", null);
|
||||||
if (testConfig == null || testConfig.equals("")) {
|
if (testConfig == null || testConfig.equals(""))
|
||||||
this.getConfiguration().setProperty("rootPass", "abcd");
|
{
|
||||||
this.getConfiguration().setProperty("rootEncrypted", false);
|
|
||||||
this.getConfiguration().save();
|
|
||||||
}
|
|
||||||
testConfig = this.getConfiguration().getString("rootUser");
|
|
||||||
if (testConfig == null || testConfig.equals("")) {
|
|
||||||
this.getConfiguration().setProperty("rootUser", "console");
|
|
||||||
this.getConfiguration().save();
|
|
||||||
}
|
|
||||||
testConfig = this.getConfiguration().getString("encryptPasswords");
|
|
||||||
if (testConfig == null || testConfig.equals("")) {
|
|
||||||
this.getConfiguration().setProperty("encryptPasswords", true);
|
this.getConfiguration().setProperty("encryptPasswords", true);
|
||||||
this.getConfiguration().save();
|
this.getConfiguration().save();
|
||||||
}
|
}
|
||||||
|
@ -223,7 +221,8 @@ public class MCTelnet extends JavaPlugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
|
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args)
|
||||||
|
{
|
||||||
if (this.isEnabled())
|
if (this.isEnabled())
|
||||||
{
|
{
|
||||||
if (cmd.getName().equals("telnetreload"))
|
if (cmd.getName().equals("telnetreload"))
|
||||||
|
@ -233,7 +232,9 @@ public class MCTelnet extends JavaPlugin {
|
||||||
this.getConfiguration().load();
|
this.getConfiguration().load();
|
||||||
testConfig();
|
testConfig();
|
||||||
if (this.getConfiguration().getBoolean("encryptPasswords", false))
|
if (this.getConfiguration().getBoolean("encryptPasswords", false))
|
||||||
|
{
|
||||||
encryptPasswords();
|
encryptPasswords();
|
||||||
|
}
|
||||||
sender.sendMessage("[MCTelnet] - Reloaded Config...");
|
sender.sendMessage("[MCTelnet] - Reloaded Config...");
|
||||||
for (int i = 0; i < clientHolder.size(); i++)
|
for (int i = 0; i < clientHolder.size(); i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,3 @@
|
||||||
/*
|
|
||||||
* To change this template, choose Tools | Templates
|
|
||||||
* and open the template in the editor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.bekvon.bukkit.mctelnet;
|
package com.bekvon.bukkit.mctelnet;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -26,17 +21,11 @@ import org.bukkit.permissions.PermissionAttachmentInfo;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.util.config.ConfigurationNode;
|
import org.bukkit.util.config.ConfigurationNode;
|
||||||
|
|
||||||
/**
|
public class TelnetListener extends Handler implements CommandSender
|
||||||
*
|
{
|
||||||
* @author Administrator
|
|
||||||
*/
|
|
||||||
public class TelnetListener extends Handler implements CommandSender {
|
|
||||||
|
|
||||||
private boolean run;
|
private boolean run;
|
||||||
private boolean isAuth;
|
private boolean isAuth;
|
||||||
private String authUser;
|
private String authUser;
|
||||||
private boolean isRoot;
|
|
||||||
|
|
||||||
private Thread listenThread;
|
private Thread listenThread;
|
||||||
Socket clientSocket;
|
Socket clientSocket;
|
||||||
MinecraftServer mcserv;
|
MinecraftServer mcserv;
|
||||||
|
@ -55,8 +44,10 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
passRegex = parent.getConfiguration().getString("passwordRegex", passRegex);
|
passRegex = parent.getConfiguration().getString("passwordRegex", passRegex);
|
||||||
commandRegex = parent.getConfiguration().getString("commandRegex", commandRegex);
|
commandRegex = parent.getConfiguration().getString("commandRegex", commandRegex);
|
||||||
ip = clientSocket.getInetAddress().toString();
|
ip = clientSocket.getInetAddress().toString();
|
||||||
listenThread = new Thread(new Runnable() {
|
listenThread = new Thread(new Runnable()
|
||||||
public void run() {
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
mainLoop();
|
mainLoop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -65,7 +56,8 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
|
|
||||||
private void mainLoop()
|
private void mainLoop()
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
instream = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
instream = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
||||||
outstream = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
|
outstream = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
|
||||||
//sendTelnetCommand(251,3);
|
//sendTelnetCommand(251,3);
|
||||||
|
@ -76,7 +68,9 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
sendTelnetCommand(253, 1);
|
sendTelnetCommand(253, 1);
|
||||||
outstream.write("[MCTelnet] - Session Started!\r\n");
|
outstream.write("[MCTelnet] - Session Started!\r\n");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
||||||
run = false;
|
run = false;
|
||||||
}
|
}
|
||||||
|
@ -87,7 +81,6 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
isAuth = true;
|
isAuth = true;
|
||||||
isRoot = true;
|
|
||||||
authUser = parent.getConfiguration().getString("rootUser");
|
authUser = parent.getConfiguration().getString("rootUser");
|
||||||
}
|
}
|
||||||
commandLoop();
|
commandLoop();
|
||||||
|
@ -99,7 +92,8 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
int retrys = 0;
|
int retrys = 0;
|
||||||
while (run && clientSocket.isConnected() && isAuth == false)
|
while (run && clientSocket.isConnected() && isAuth == false)
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
outstream.write("Username:");
|
outstream.write("Username:");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
String username = instream.readLine().replaceAll(passRegex, "");
|
String username = instream.readLine().replaceAll(passRegex, "");
|
||||||
|
@ -111,148 +105,88 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
outstream.write("\r\n");
|
outstream.write("\r\n");
|
||||||
sendTelnetCommand(252, 1);
|
sendTelnetCommand(252, 1);
|
||||||
sendTelnetCommand(253, 1);
|
sendTelnetCommand(253, 1);
|
||||||
String rootuser = parent.getConfiguration().getString("rootUser");
|
|
||||||
String rootpass = parent.getConfiguration().getString("rootPass");
|
|
||||||
if (rootuser != null && !rootuser.equals("") && username.equals(rootuser)) {
|
|
||||||
if(parent.getConfiguration().getBoolean("rootEncrypted",false))
|
|
||||||
{
|
|
||||||
pw = MCTelnet.hashPassword(pw);
|
|
||||||
}
|
|
||||||
if (rootpass != null && !rootpass.equals("") && pw.equals(rootpass)) {
|
|
||||||
authUser = rootuser;
|
|
||||||
isAuth = true;
|
|
||||||
isRoot = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ConfigurationNode parentnode = parent.getConfiguration().getNode("users");
|
ConfigurationNode parentnode = parent.getConfiguration().getNode("users");
|
||||||
if (parentnode != null) {
|
if (parentnode != null)
|
||||||
|
{
|
||||||
ConfigurationNode usernode = parentnode.getNode(username);
|
ConfigurationNode usernode = parentnode.getNode(username);
|
||||||
if (usernode != null) {
|
if (usernode != null)
|
||||||
|
{
|
||||||
String userpw = usernode.getString("password");
|
String userpw = usernode.getString("password");
|
||||||
if (usernode.getBoolean("passEncrypted", false))
|
if (usernode.getBoolean("passEncrypted", false))
|
||||||
{
|
{
|
||||||
pw = MCTelnet.hashPassword(pw);
|
pw = MCTelnet.hashPassword(pw);
|
||||||
}
|
}
|
||||||
if (pw.equals(userpw)) {
|
if (pw.equals(userpw))
|
||||||
|
{
|
||||||
authUser = username;
|
authUser = username;
|
||||||
isAuth = true;
|
isAuth = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (isAuth)
|
||||||
if (isAuth) {
|
{
|
||||||
outstream.write("Logged In as " + authUser + "!\r\n:");
|
outstream.write("Logged In as " + authUser + "!\r\n:");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
Thread.sleep(2000);
|
Thread.sleep(2000);
|
||||||
outstream.write("Invalid Username or Password!\r\n\r\n");
|
outstream.write("Invalid Username or Password!\r\n\r\n");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
}
|
}
|
||||||
retrys++;
|
retrys++;
|
||||||
if (retrys == 3 && isAuth == false) {
|
if (retrys == 3 && isAuth == false)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outstream.write("Too many failed login attempts!");
|
outstream.write("Too many failed login attempts!");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} catch (Exception ex)
|
}
|
||||||
{ }
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
run = false;
|
run = false;
|
||||||
authUser = null;
|
authUser = null;
|
||||||
isAuth = false;
|
isAuth = false;
|
||||||
isRoot = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commandLoop() {
|
private void commandLoop()
|
||||||
try {
|
{
|
||||||
if (isAuth) {
|
try
|
||||||
ConfigurationNode usernode;
|
{
|
||||||
String commands = "";
|
if (isAuth)
|
||||||
|
{
|
||||||
String[] validCommands = new String[0];
|
String[] validCommands = new String[0];
|
||||||
if (isRoot) {
|
|
||||||
Logger.getLogger("Minecraft").addHandler(this);
|
Logger.getLogger("Minecraft").addHandler(this);
|
||||||
} else {
|
|
||||||
usernode = parent.getConfiguration().getNode("users").getNode(authUser);
|
while (run && clientSocket.isConnected() && isAuth)
|
||||||
if (usernode != null) {
|
{
|
||||||
if (usernode.getString("log", "false").equals("true")) {
|
|
||||||
Logger.getLogger("Minecraft").addHandler(this);
|
|
||||||
}
|
|
||||||
commands = usernode.getString("commands");
|
|
||||||
if (commands != null) {
|
|
||||||
validCommands = commands.split("\\,");
|
|
||||||
}
|
|
||||||
for (int i = 0; i < validCommands.length; i++) {
|
|
||||||
String thisCommand = validCommands[i];
|
|
||||||
validCommands[i] = thisCommand.trim();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (run && clientSocket.isConnected() && isAuth) {
|
|
||||||
String command = "";
|
String command = "";
|
||||||
command = instream.readLine().replaceAll(commandRegex, "");
|
command = instream.readLine().replaceAll(commandRegex, "");
|
||||||
if (command.equals("exit")) {
|
if (command.equals("exit"))
|
||||||
|
{
|
||||||
run = false;
|
run = false;
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
boolean elevate = false;
|
if (!clientSocket.isClosed())
|
||||||
boolean allowCommand = false;
|
|
||||||
if(command.startsWith("sudo "))
|
|
||||||
{
|
{
|
||||||
if(!isRoot)
|
|
||||||
{
|
|
||||||
elevate = true;
|
|
||||||
}
|
|
||||||
command = command.substring(5);
|
|
||||||
}
|
|
||||||
if (!isRoot) {
|
|
||||||
for (int i = 0; i < validCommands.length; i++) {
|
|
||||||
if (command.equals(validCommands[i]) || (command.startsWith(validCommands[i] + " "))) {
|
|
||||||
allowCommand = true;
|
|
||||||
i = validCommands.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(elevate && !allowCommand)
|
|
||||||
{
|
|
||||||
sendTelnetCommand(251,1);
|
|
||||||
sendTelnetCommand(254,1);
|
|
||||||
outstream.write("Root Password:");
|
|
||||||
outstream.flush();
|
|
||||||
String pw = instream.readLine().replaceAll(passRegex, "");
|
|
||||||
outstream.write("\r\n");
|
|
||||||
sendTelnetCommand(252,1);
|
|
||||||
sendTelnetCommand(253,1);
|
|
||||||
String rootpass = parent.getConfiguration().getString("rootPass");
|
|
||||||
if(parent.getConfiguration().getBoolean("rootEncrypted", false))
|
|
||||||
{
|
|
||||||
pw=MCTelnet.hashPassword(pw);
|
|
||||||
}
|
|
||||||
if (pw.equals(rootpass)) {
|
|
||||||
allowCommand = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!clientSocket.isClosed()) {
|
|
||||||
if (isRoot || allowCommand) {
|
|
||||||
//((CraftServer)getServer()).dispatchCommand(new ConsoleCommandSender(getServer()), command);
|
|
||||||
//mcserv.issueCommand(command, this);
|
|
||||||
parent.getServer().dispatchCommand(this, command);
|
parent.getServer().dispatchCommand(this, command);
|
||||||
System.out.println("[MCTelnet] " + authUser + " issued command: " + command);
|
System.out.println("[MCTelnet] " + authUser + " issued command: " + command);
|
||||||
} else {
|
}
|
||||||
if(!command.equals(""))
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
outstream.write("You do not have permission to use this command...\r\n:");
|
|
||||||
outstream.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,16 +197,21 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
|
|
||||||
public void killClient()
|
public void killClient()
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
run = false;
|
run = false;
|
||||||
outstream.write("[MCTelnet] - Closing Connection!");
|
outstream.write("[MCTelnet] - Closing Connection!");
|
||||||
clientSocket.close();
|
clientSocket.close();
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shutdown() {
|
private void shutdown()
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
run = false;
|
run = false;
|
||||||
Logger.getLogger("Minecraft").removeHandler(this);
|
Logger.getLogger("Minecraft").removeHandler(this);
|
||||||
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] Closing connection: " + ip);
|
Logger.getLogger("Minecraft").log(Level.INFO, "[MCTelnet] Closing connection: " + ip);
|
||||||
|
@ -283,66 +222,87 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
}
|
}
|
||||||
mcserv = null;
|
mcserv = null;
|
||||||
parent = null;
|
parent = null;
|
||||||
} catch (Exception ex) {
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
Logger.getLogger("Minecraft").log(Level.SEVERE, null, ex);
|
||||||
run = false;
|
run = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void publish(LogRecord record) {
|
public void publish(LogRecord record)
|
||||||
try {
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
if (!clientSocket.isClosed())
|
if (!clientSocket.isClosed())
|
||||||
{
|
{
|
||||||
outstream.write(ChatColor.stripColor(record.getMessage()) + "\r\n:");
|
outstream.write(ChatColor.stripColor(record.getMessage()) + "\r\n:");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void flush() {
|
public void flush()
|
||||||
|
{
|
||||||
if (clientSocket.isConnected())
|
if (clientSocket.isConnected())
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendMessage(String string)
|
||||||
public void sendMessage(String string) {
|
{
|
||||||
if (clientSocket.isConnected())
|
if (clientSocket.isConnected())
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
string = ChatColor.stripColor(string);
|
string = ChatColor.stripColor(string);
|
||||||
outstream.write(string + "\r\n:");
|
outstream.write(string + "\r\n:");
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOp() {
|
public boolean isOp()
|
||||||
|
{
|
||||||
if (authUser.equalsIgnoreCase("console"))
|
if (authUser.equalsIgnoreCase("console"))
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
if (parent.getConfiguration().getBoolean("allowOPsAll", false))
|
if (parent.getConfiguration().getBoolean("allowOPsAll", false))
|
||||||
|
{
|
||||||
return parent.getServer().getPlayer(authUser).isOp();
|
return parent.getServer().getPlayer(authUser).isOp();
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPlayer() {
|
public boolean isPlayer()
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Server getServer() {
|
public Server getServer()
|
||||||
|
{
|
||||||
return parent.getServer();
|
return parent.getServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws SecurityException {
|
public void close() throws SecurityException
|
||||||
|
{
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,64 +310,80 @@ public class TelnetListener extends Handler implements CommandSender {
|
||||||
{
|
{
|
||||||
if (clientSocket.isConnected())
|
if (clientSocket.isConnected())
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
String tcmd = ("" + ((char) 255) + ((char) command) + ((char) option));
|
String tcmd = ("" + ((char) 255) + ((char) command) + ((char) option));
|
||||||
outstream.write(tcmd);
|
outstream.write(tcmd);
|
||||||
outstream.flush();
|
outstream.flush();
|
||||||
} catch (IOException ex) {
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName()
|
||||||
|
{
|
||||||
return authUser;
|
return authUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPermissionSet(String string) {
|
public boolean isPermissionSet(String string)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPermissionSet(Permission prmsn) {
|
public boolean isPermissionSet(Permission prmsn)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPermission(String string) {
|
public boolean hasPermission(String string)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasPermission(Permission prmsn) {
|
public boolean hasPermission(Permission prmsn)
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln) {
|
public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin) {
|
public PermissionAttachment addAttachment(Plugin plugin)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln, int i) {
|
public PermissionAttachment addAttachment(Plugin plugin, String string, boolean bln, int i)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PermissionAttachment addAttachment(Plugin plugin, int i) {
|
public PermissionAttachment addAttachment(Plugin plugin, int i)
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAttachment(PermissionAttachment pa) {
|
public void removeAttachment(PermissionAttachment pa)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recalculatePermissions() {
|
public void recalculatePermissions()
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<PermissionAttachmentInfo> getEffectivePermissions() {
|
public Set<PermissionAttachmentInfo> getEffectivePermissions()
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOp(boolean bln) {
|
public void setOp(boolean bln)
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue