commit 48b4173a7a8f93c95683b2fdef066ae5fd696614 Author: Linux User Date: Fri May 15 19:10:11 2020 -0400 Inital commit diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..028713a --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +The slash.monster server code diff --git a/README.md b/README.md new file mode 100644 index 0000000..5e2ea42 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# Misc notes. + +We need to enable STATX in kore instead of STAT. One of the places seems to have +ifdef garuds around it, just added it to the other place. + +We need to comment out the restrictions on mprotect around line 99 of src/seccomp.c in order for luajit to be able to do it's thing diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000..c2e77ad Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/milligram.css b/assets/milligram.css new file mode 100644 index 0000000..85f877b --- /dev/null +++ b/assets/milligram.css @@ -0,0 +1,11 @@ +/*! + * Milligram v1.3.0 + * https://milligram.github.io + * + * Copyright (c) 2017 CJ Patoilo + * Licensed under the MIT license + */ + +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{-ms-grid-row-align:center;align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/*# sourceMappingURL=milligram.min.css.map */ \ No newline at end of file diff --git a/assets/milligram.min.css.map b/assets/milligram.min.css.map new file mode 100644 index 0000000..586fbb3 --- /dev/null +++ b/assets/milligram.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["milligram.min.css"],"names":[],"mappings":"AAAA,mBAAmB,kBAAkB,CAAC,KAAK,sBAAsB,eAAe,CAAC,KAAK,cAAc,yEAAyE,gBAAgB,gBAAgB,qBAAqB,eAAe,CAAC,WAAW,iCAAiC,cAAc,eAAe,mBAAmB,CAAC,wBAAwB,eAAe,CAAC,6EAA6E,yBAAyB,4BAA4B,oBAAoB,WAAW,eAAe,qBAAqB,iBAAiB,gBAAgB,cAAc,qBAAqB,mBAAmB,iBAAiB,kBAAkB,qBAAqB,yBAAyB,kBAAkB,CAAC,sNAAsN,yBAAyB,qBAAqB,WAAW,SAAS,CAAC,+HAA+H,eAAe,UAAU,CAAC,0TAA0T,yBAAyB,oBAAoB,CAAC,wJAAwJ,6BAA6B,aAAa,CAAC,4WAA4W,6BAA6B,qBAAqB,aAAa,CAAC,gdAAgd,qBAAqB,aAAa,CAAC,8IAA8I,6BAA6B,yBAAyB,aAAa,CAAC,wVAAwV,6BAA6B,yBAAyB,aAAa,CAAC,4bAA4b,aAAa,CAAC,KAAK,mBAAmB,oBAAoB,cAAc,eAAe,oBAAoB,kBAAkB,CAAC,IAAI,mBAAmB,iCAAiC,iBAAiB,CAAC,SAAS,gBAAgB,cAAc,oBAAoB,eAAe,CAAC,GAAG,SAAS,gCAAgC,eAAe,CAAC,4JAA4J,wBAAgB,AAAhB,qBAAgB,AAAhB,gBAAgB,6BAA6B,4BAA4B,oBAAoB,gBAAgB,mBAAmB,cAAc,qBAAqB,UAAU,CAAC,kNAAkN,qBAAqB,SAAS,CAAC,OAAO,mOAAmO,oBAAoB,CAAC,aAAa,iNAAiN,CAAC,SAAS,iBAAiB,CAAC,aAAa,cAAc,iBAAiB,gBAAgB,mBAAmB,CAAC,SAAS,eAAe,SAAS,CAAC,2CAA2C,cAAc,CAAC,cAAc,qBAAqB,mBAAmB,iBAAiB,CAAC,WAAW,cAAc,mBAAmB,iBAAiB,kBAAkB,UAAU,CAAC,KAAK,aAAa,sBAAsB,UAAU,UAAU,CAAC,oBAAoB,SAAS,CAAC,4BAA4B,SAAS,CAAC,cAAc,cAAc,CAAC,aAAa,sBAAsB,CAAC,gBAAgB,oBAAoB,CAAC,gBAAgB,kBAAkB,CAAC,iBAAiB,mBAAmB,CAAC,kBAAkB,oBAAoB,CAAC,aAAa,cAAc,cAAc,cAAc,eAAe,UAAU,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,4DAA4D,oBAAoB,CAAC,8BAA8B,eAAe,CAAC,4DAA4D,oBAAoB,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,8CAA8C,kBAAkB,kBAAkB,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,8CAA8C,kBAAkB,kBAAkB,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,uBAAuB,aAAa,aAAa,CAAC,yBAAyB,qBAAqB,CAAC,4BAA4B,mBAAmB,CAAC,4BAA4B,0BAAiB,AAAjB,iBAAiB,CAAC,0BAA0B,KAAK,mBAAmB,oBAAoB,yBAAyB,CAAC,aAAa,sBAAsB,gBAAgB,CAAC,CAAC,EAAE,cAAc,oBAAoB,CAAC,gBAAgB,aAAa,CAAC,SAAS,gBAAgB,aAAa,cAAc,CAAC,sDAAsD,cAAc,6BAA6B,CAAC,GAAG,yBAAyB,CAAC,GAAG,wBAAwB,CAAC,wBAAwB,oBAAoB,CAAC,+BAA+B,oBAAoB,CAAC,4CAA4C,oBAAoB,CAAC,MAAM,iBAAiB,UAAU,CAAC,MAAM,mCAAmC,sBAAsB,eAAe,CAAC,8BAA8B,cAAc,CAAC,4BAA4B,eAAe,CAAC,SAAS,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,gBAAgB,sBAAsB,qBAAqB,YAAY,CAAC,GAAG,iBAAiB,eAAe,CAAC,GAAG,iBAAiB,gBAAgB,CAAC,GAAG,iBAAiB,eAAe,CAAC,GAAG,iBAAiB,uBAAuB,gBAAgB,CAAC,GAAG,iBAAiB,uBAAuB,eAAe,CAAC,GAAG,iBAAiB,iBAAiB,eAAe,CAAC,IAAI,cAAc,CAAC,gBAAgB,WAAW,YAAY,aAAa,CAAC,YAAY,UAAU,CAAC,aAAa,WAAW,CAAC","file":"milligram.min.css","sourcesContent":["*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='email'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],textarea,select{appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem;width:100%}input[type='email']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem;text-align:left}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}\n"]} \ No newline at end of file diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..72660ec --- /dev/null +++ b/assets/style.css @@ -0,0 +1,14 @@ +body{ + margin:40px auto; + max-width:80%; + line-height:1.6; + font-size:18px; + color:#444 !important; + padding:0 10px +} +h1,h2,h3{line-height:1.2} +a,a:visited{color:#f99} +.spoiler,.spoiler2{background:#444} +.spoiler:hover,.spoiler2:hover{color:#FFF} +.greentext{color:#282} +.pinktext{color:#928} diff --git a/conf/build.conf b/conf/build.conf new file mode 100644 index 0000000..7e98536 --- /dev/null +++ b/conf/build.conf @@ -0,0 +1,38 @@ +# smr build config +# You can switch flavors using: kodev flavor [newflavor] + +# Set to yes if you wish to produce a single binary instead +# of a dynamic library. If you set this to yes you must also +# set kore_source together with kore_flavor. +#single_binary=no +#kore_source=/home/joris/src/kore +#kore_flavor= + +# The flags below are shared between flavors +cflags=-Wall -Wmissing-declarations -Wshadow +cflags=-Wstrict-prototypes -Wmissing-prototypes +cflags=-Wpointer-arith -Wcast-qual -Wsign-compare + +cxxflags=-Wall -Wmissing-declarations -Wshadow +cxxflags=-Wpointer-arith -Wcast-qual -Wsign-compare + +# Mime types for assets served via the builtin asset_serve_* +mime_add=txt:text/plain; charset=utf-8 +mime_add=png:image/png +mime_add=html:text/html; charset=utf-8 +mime_add=css:text/css + +dev { + # These flags are added to the shared ones when + # you build the "dev" flavor. + ldflags=-llua + cflags=-g + cxxflags=-g +} + +prod { + cflags=-D BUILD_PROD + cflags=-I/usr/include/luajit-2.1 + cflags=-lluajit-5.1 + ldflags=-lluajit-5.1 +} diff --git a/conf/smr.conf b/conf/smr.conf new file mode 100644 index 0000000..4a85c2e --- /dev/null +++ b/conf/smr.conf @@ -0,0 +1,73 @@ +# smr configuration + +server tls { + bind 0.0.0.0 8888 +} + +seccomp_tracing yes +load ./smr.so +root kore_chroot +runas root +#keymgr_runas demo +#keymgr_root ./ +workers 1 + +http_body_max 8388608 + +tls_dhparam dh2048.pem + +validator v_any regex .* +validator v_storyid regex [a-zA-Z0-9]+ +validator v_subdomain regex [a-z0-9]{1,30} +validator v_markup regex (plain|imageboard) + +domain * { + attach tls + + certfile server.pem + certkey key.pem + + #I run kore behind a lighttpd reverse proxy, so this is a bit useless to me + #accesslog /dev/null + accesslog kore_access.log + + route / home + route /_css/style.css asset_serve_style_css + route /_css/milligram.css asset_serve_milligram_css + route /_css/milligram.min.css.map asset_serve_milligram_min_css_map + route /favicon.ico asset_serve_favicon_ico + route /_paste post_story + route /_edit edit_story + route /_bio edit_bio + route /_login login + route /_claim claim + # Leading ^ is needed for dynamic routes, kore says the route is dynamic if it does not start with '/' + route ^/[^_].* read_story + + params get /_edit { + validate story v_storyid + } + params post /_edit { + validate title v_any + validate story v_storyid + validate text v_any + validate pasteas v_subdomain + validate markup v_markup + } + params post /_paste { + validate title v_any + validate text v_any + validate pasteas v_subdomain + validate markup v_markup + } + #params get /[^_].* { + #validate story v_storyid + #} + params post /_login { + validate user v_subdomain + validate pass v_any + } + params post /_claim { + validate user v_any + } +} diff --git a/dh2048.pem b/dh2048.pem new file mode 100644 index 0000000..511de69 --- /dev/null +++ b/dh2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAn4f4Qn5SudFjEYPWTbUaOTLUH85YWmmPFW1+b5bRa9ygr+1wfamv +VKVT7jO8c4msSNikUf6eEfoH0H4VTCaj+Habwu+Sj+I416r3mliMD4SjNsUJrBrY +Y0QV3ZUgZz4A8ARk/WwQcRl8+ZXJz34IaLwAcpyNhoV46iHVxW0ty8ND0U4DIku/ +PNayKimu4BXWXk4RfwNVP59t8DQKqjshZ4fDnbotskmSZ+e+FHrd+Kvrq/WButvV +Bzy9fYgnUlJ82g/bziCI83R2xAdtH014fR63MpElkqdNeChb94pPbEdFlNUvYIBN +xx2vTUQMqRbB4UdG2zuzzr5j98HDdblQ+wIBAg== +-----END DH PARAMETERS----- \ No newline at end of file diff --git a/src/keccak.c b/src/keccak.c new file mode 100644 index 0000000..201af68 --- /dev/null +++ b/src/keccak.c @@ -0,0 +1,36 @@ +#define FOR(i,n) for(i=0; i>1; } +#define ROL(a,o) ((((u64)a)<>(64-o))) +static u64 load64(const u8 *x) { ui i; u64 u=0; FOR(i,8) { u<<=8; u|=x[7-i]; } return u; } +static void store64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]=u; u>>=8; } } +static void xor64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]^=u; u>>=8; } } +#define rL(x,y) load64((u8*)s+8*(x+5*y)) +#define wL(x,y,l) store64((u8*)s+8*(x+5*y),l) +#define XL(x,y,l) xor64((u8*)s+8*(x+5*y),l) +void KeccakF1600(void *s) +{ + ui r,x,y,i,j,Y; u8 R=0x01; u64 C[5],D; + for(i=0; i<24; i++) { + /*θ*/ FOR(x,5) C[x]=rL(x,0)^rL(x,1)^rL(x,2)^rL(x,3)^rL(x,4); FOR(x,5) { D=C[(x+4)%5]^ROL(C[(x+1)%5],1); FOR(y,5) XL(x,y,D); } + /*ρπ*/ x=1; y=r=0; D=rL(x,y); FOR(j,24) { r+=j+1; Y=(2*x+3*y)%5; x=y; y=Y; C[0]=rL(x,y); wL(x,y,ROL(D,r%64)); D=C[0]; } + /*χ*/ FOR(y,5) { FOR(x,5) C[x]=rL(x,y); FOR(x,5) wL(x,y,C[x]^((~C[(x+1)%5])&C[(x+2)%5])); } + /*ι*/ FOR(j,7) if (LFSR86540(&R)) XL(0,0,(u64)1<<((1<0) { b=(inLen0) { b=(outLen0) KeccakF1600(s); } +} diff --git a/src/keccak.h b/src/keccak.h new file mode 100644 index 0000000..0eef59b --- /dev/null +++ b/src/keccak.h @@ -0,0 +1,4 @@ +typedef unsigned char u8; +typedef unsigned long long int u64; +typedef unsigned int ui; +void FIPS202_SHA3_512(const u8 *in, u64 inLen, u8 *out); diff --git a/src/libcrypto.c b/src/libcrypto.c new file mode 100644 index 0000000..41ccf6c --- /dev/null +++ b/src/libcrypto.c @@ -0,0 +1,64 @@ +/* +borrowed sha3 implementation from keccak.team +*/ +#ifdef BUILD_PROD +#include +#endif +#include +#include +#include +#include "libcrypto.h" +#include "keccak.h" + +/* +sha3(data::string)::string +*/ +int +lsha3(lua_State *L){ + size_t len; + char out[64]; + const char *data = luaL_checklstring(L,-1,&len); + lua_pop(L,1); + printf("All data gotten, about to hash\n"); + FIPS202_SHA3_512(data, len, out); + printf("Finished hashing\n"); + lua_pushlstring(L,out,64); + printf("Finished pushing string to lua\n"); + return 1; +} + +/* +string xor +sxor(a::string, b::string)::string +*/ +int +lsxor(lua_State *L){ + size_t la,lb; + const char *a = luaL_checklstring(L,-1,&la); + const char *b = luaL_checklstring(L,-2,&lb); + size_t outsize = la > lb ? la : lb; + size_t loopsize = la > lb ? lb : la; + const char *shorter = la > lb ? b : a; + const char *longer = la > lb ? a : b; + char out[outsize]; + int i; + for(i = 0; i < loopsize; i++) + out[i] = shorter[i] ^ longer[i]; + for(;i < outsize; i++) + out[i] = longer[i]; + lua_pushlstring(L,out,outsize); + return 1; +} + +static const luaL_Reg crypto_funcs[] = { + {"sha3", lsha3}, + {"sxor", lsxor}, + {NULL,NULL} +}; + +void +load_crypto_libs(lua_State *L){ + lua_getglobal(L,"_G"); + luaL_register(L,NULL,crypto_funcs); + lua_pop(L,1); +} diff --git a/src/libcrypto.h b/src/libcrypto.h new file mode 100644 index 0000000..7bb9afc --- /dev/null +++ b/src/libcrypto.h @@ -0,0 +1,4 @@ + +int lsha3(lua_State *L); +int lsxor(lua_State *L); +void load_crypto_libs(lua_State *L); diff --git a/src/libkore.c b/src/libkore.c new file mode 100644 index 0000000..3aaeda7 --- /dev/null +++ b/src/libkore.c @@ -0,0 +1,292 @@ +/*Export kore's functions to lua*/ +#include +#include +#ifdef BUILD_PROD +#include +#endif +#include +#include +#include +//#include //linux only I guess +#include "libkore.h" + +struct http_request* +luaL_checkrequest(lua_State *L, int pos){ + if(!lua_isuserdata(L,pos)){ + lua_pushstring(L,"Bad argument, expected userdata, got "); + lua_pushstring(L,lua_typename(L,lua_type(L,pos))); + lua_concat(L,2); + lua_error(L); + } + return lua_touserdata(L,pos); +} + +/* +http_response(request::userdata, errcode::number, data::string) +*/ +int +lhttp_response(lua_State *L){ + size_t size; + const char *data = luaL_checklstring(L,-1,&size); + int httpcode = luaL_checkint(L,-2); + struct http_request *req = luaL_checkrequest(L,-3); + http_response(req,httpcode,data,size); + lua_pop(L,3); + return 0; +} + +/* +http_method_text(request::userdata)::string +*/ +int +lhttp_method_text(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + const char *method = http_method_text(req->method); + lua_pushstring(L,method); + return 1; +} + +/* +http_request_get_path(request::userdata)::string +*/ +int +lhttp_request_get_path(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + const char *path = req->path; + lua_pushstring(L,path); + return 1; +} + +/* +http_request_get_host(request::userdata)::string +*/ +int +lhttp_request_get_host(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + const char *host = req->host; + lua_pushstring(L,host); + return 1; +} +/* +http_request_populate_post(request::userdata) +*/ +int +lhttp_request_populate_post(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + http_populate_post(req); + return 0; +} + +/* +http_request_populate_qs(request::userdata) +*/ +int +lhttp_request_populate_qs(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + http_populate_qs(req); + return 0; +} + +/* +http_response_header(request::userdata, header::string, value::string) +*/ +int +lhttp_response_header(lua_State *L){ + const char *value = luaL_checkstring(L,-1); + const char *header = luaL_checkstring(L,-2); + struct http_request *req = luaL_checkrequest(L,-3); + lua_pop(L,3); + http_response_header(req, header, value); + return 0; +} + +/* +http_response_cookie(req::userdata, name::string, value::string, path::string, expires::number, maxage::number) +*/ +int +lhttp_response_cookie(lua_State *L){ + //int flags = luaL_checkint(L,-1);//TODO: flags + int maxage = luaL_checkint(L,-1); + int expires = luaL_checkint(L,-2); + const char *path = luaL_checkstring(L,-3); + const char *value = luaL_checkstring(L,-4); + const char *name = luaL_checkstring(L,-5); + struct http_request *req = luaL_checkrequest(L,-6); + lua_pop(L,6); + http_response_cookie(req,name,value,path,expires,maxage,NULL); + return 0; +} + +/* +http_request_cookie(req::userdata, name::string)::string | nil +*/ +int +lhttp_request_cookie(lua_State *L){ + char *value; + const char *name = luaL_checkstring(L,-1); + struct http_request *req = luaL_checkrequest(L,-2); + lua_pop(L,2); + if(http_request_cookie(req,name,&value)){ + lua_pushstring(L,value); + }else{ + lua_pushnil(L); + } + return 1; +} + +/* +This method may fail, if it does, it will return false plus an error message +http_argument_get_string(request::userdata, name::string)::(string || false, string) +*/ +int +lhttp_argument_get_string(lua_State *L){ + const char *name = luaL_checkstring(L,-1); + struct http_request *req = luaL_checkrequest(L,-2); + lua_pop(L,2); + const char *value; + int err = http_argument_get_string(req,name,&value); + if(err == KORE_RESULT_OK){ + lua_pushstring(L,value); + return 1; + }else{ + lua_pushboolean(L,0); + lua_pushstring(L,"Failed to find argument: "); + lua_pushstring(L,name); + lua_concat(L,2); + return 2; + } +} +/* +Get the ip of this connection, ipv6 supported +http_request_get_ip(request::userdata)::string +*/ +int +lhttp_request_get_ip(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + char addr[INET6_ADDRSTRLEN]; + printf("AF_INET:%d\n",AF_INET); + printf("AF_INET6:%d\n",AF_INET6); + printf("AF_UNIX:%d\n",AF_UNIX); + switch(req->owner->family){ + case AF_INET: + inet_ntop( + req->owner->family, + &(req->owner->addr.ipv4.sin_addr), + addr, + sizeof(addr) + ); + break; + case AF_INET6: + inet_ntop( + req->owner->family, + &(req->owner->addr.ipv6.sin6_addr), + addr, + sizeof(addr) + ); + break; + case AF_UNIX: + default: + lua_pushstring(L,"Tried to get IP from unknown socket family:"); + lua_getglobal(L,"tostring"); + lua_pushnumber(L,req->owner->family); + lua_call(L,1,1); + lua_concat(L,2); + lua_error(L); + break; + } + lua_pushstring(L,addr); + return 1; + +} + +/* +http_populate_cookies(request::userdata) +*/ +int +lhttp_populate_cookies(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + http_populate_cookies(req); + return 0; +} + +/* +http_populate_multipart_form(request::userdata) +*/ +int +lhttp_populate_multipart_form(lua_State *L){ + struct http_request *req = luaL_checkrequest(L,-1); + lua_pop(L,1); + http_populate_multipart_form(req); + return 0; +} + +/* +http_file_get(request::userdata, name::string)::string +*/ +int +lhttp_file_get(lua_State *L){ + const char *name = luaL_checkstring(L,-1); + struct http_request *req = luaL_checkrequest(L,-2); + lua_pop(L,2); + struct http_file *f = http_file_lookup(req, name); + if(f == NULL){ + lua_pushstring(L,"No such file:"); + lua_pushstring(L,name); + lua_concat(L,2); + lua_error(L); + } + printf("file length: %d\n", f->length); + char s[f->length + 1]; + size_t read = http_file_read(f,s,f->length); + if(read < f->length){ + lua_pushstring(L,"Failed to read contents of:"); + lua_pushstring(L,name); + lua_concat(L,2); + lua_error(L); + } + s[f->length] = '\0'; + lua_pushstring(L,s); + return 1; +} + +static const luaL_Reg kore_funcs[] = { + {"http_response", lhttp_response}, + {"http_response_header", lhttp_response_header}, + {"http_method_text",lhttp_method_text}, + {"http_request_get_path",lhttp_request_get_path}, + {"http_request_get_host",lhttp_request_get_host}, + {"http_request_populate_post",lhttp_request_populate_post}, + {"http_request_populate_qs",lhttp_request_populate_qs}, + {"http_request_cookie",lhttp_request_cookie}, + {"http_response_cookie",lhttp_response_cookie}, + {"http_argument_get_string",lhttp_argument_get_string}, + {"http_request_get_ip",lhttp_request_get_ip}, + {"http_populate_cookies",lhttp_populate_cookies}, + {"http_populate_multipart_form",lhttp_populate_multipart_form}, + {"http_file_get",lhttp_file_get}, + {NULL,NULL} +}; + +static const luaL_Reg http_request_meta[] = { + {NULL,NULL} +}; + +void +load_kore_libs(lua_State *L){ + luaL_newmetatable(L,"http_request");//{m_http_request} + lua_newtable(L);//{m_http_request},{} + luaL_register(L,NULL,http_request_meta);//{m_http_request},{meta} + lua_setfield(L,-2,"__index");//{m_http_request} + lua_pop(L,1); + + lua_getglobal(L,"_G"); + luaL_register(L,NULL,kore_funcs); + lua_pop(L,1); +} diff --git a/src/libkore.h b/src/libkore.h new file mode 100644 index 0000000..1336868 --- /dev/null +++ b/src/libkore.h @@ -0,0 +1,16 @@ + +int lhttp_response(lua_State *L); +int lhttp_response_header(lua_State *L); +int lhttp_method_text(lua_State *L); +int lhttp_request_get_path(lua_State *L); +int lhttp_request_get_host(lua_State *L); +int lhttp_request_populate_post(lua_State *L); +int lhttp_response_cookie(lua_State *L); +int lhttp_request_cookie(lua_State *L); +int lhttp_argument_get_string(lua_State *L); +int lhttp_request_get_ip(lua_State *L); +int lhttp_populate_cookies(lua_State *L); +int lhttp_file_get(lua_State *L); +int lhttp_populate_multipart_form(lua_State *L); +void load_kore_libs(lua_State *L); +struct http_request* luaL_checkrequest(lua_State *L, int pos); diff --git a/src/lua/init.lua b/src/lua/init.lua new file mode 100644 index 0000000..2fd6a01 --- /dev/null +++ b/src/lua/init.lua @@ -0,0 +1,1027 @@ +print("very quick hello from init.lua") +local et = require("etlua") +local sql = require("lsqlite3") +local zlib = require("zlib") +--local function print() end --squash prints +print("Hello from init.lua") +local parser_names = {"plain","imageboard"} +local parsers = {} +for _,v in pairs(parser_names) do + parsers[v] = require("parser_" .. v) +end +local db,cache +local domain = "test.monster:8888" +local pagenames = { + "index", + "author_index", + "claim", + "paste", + "read", + "nostory", + "noauthor", + "login", + "author_paste", + "author_edit", +} +local pages = {} +local stmnt_index, stmnt_author_index, stmnt_read, stmnt_paste, stmnt_raw +local stmnt_author_create, stmnt_author_acct, stmnt_author_bio +local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache +local stmnt_get_session, stmnt_insert_session +local stmnt_edit +--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/ +--no underscore because we use that for our operative pages +local url_characters = + [[abcdefghijklmnopqrstuvwxyz]].. + [[ABCDEFGHIJKLMNOPQRSTUVWXYZ]].. + [[0123456789]].. + [[$-+!*'(),]] +local url_characters_rev = {} +for i = 1,string.len(url_characters) do + url_characters_rev[string.sub(url_characters,i,i)] = i +end +local function decodeentities(capture) + local n = tonumber(capture,16) + local c = string.char(n) + if escapes[c] then + return escapes[c] + else + return c + end +end + +local function sqlassert(...) + local r,errcode,err = ... + if not r then + error(string.format("%d: %s",errcode, err)) + end + return r +end + +print("Hello from init.lua") +function configure() + db = sqlassert(sql.open("data/posts.db")) + cache = sqlassert(sql.open_memory()) + for k,v in pairs(pagenames) do + print("Compiling page:",v) + local f = assert(io.open("pages/" .. v .. ".etlua","r")) + pages[v] = assert(et.compile(f:read("*a"))) + f:close() + end + print("Compiled pages...") + local msg = "test message" + local one = zlib.compress(msg) + local two = zlib.decompress(one) + --For some reason, the zlib library fails if this is done as a oneliner + assert(two == msg, "zlib not working as expected") + print("zlib seems to work...") + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS authors ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT UNIQUE ON CONFLICT FAIL, + salt BLOB, + passhash BLOB, + joindate INTEGER, + biography TEXT + ); + ]])) + --Create a fake "anonymous" user, so we don't run into trouble + --so that no one runs into touble being able to paste under this account. + assert(db:exec([[ + INSERT OR IGNORE INTO authors ( + name, + salt, + passhash, + joindate, + biography + ) VALUES ( + 'anonymous', + '', + '', + strftime('%s','1970-01-01 00:00:00'), + '' + ); + ]])) + assert(db:exec([[ + REPLACE INTO authors (name,salt,passhash,joindate,biography) VALUES ( + 'anonymous', + '', + '', + strftime('%s','1970-01-01 00:00:00'), + '', + ); + ]])) + --If/when an author delets their account, all posts + --and comments by that author are also deleted (on + --delete cascade) this is intentional. This also + --means that all comments by other users on a post + --an author makes will also be deleted. + -- + --Post text uses zlib compression + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS posts ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + post_text BLOB, + post_title TEXT, + authorid REFERENCES authors(id) ON DELETE CASCADE, + isanon INTEGER, + hashedip BLOB, + post_time INTEGER + ); + ]])) + --Store the raw text so people can download it later, maybe + --we can use it for "download as image" or "download as pdf" + --in the future too. Stil stored zlib compressed + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS raw_text ( + id INTEGER PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE, + post_text BLOB, + markup TEXT + );]])) + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS images ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + name TEXT, + image BLOB, + authorid REFERENCES authors(id) ON DELETE CASCADE, + upload_time INTEGER, + hashedip BLOB + ); + ]])) + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS comments ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + postid REFERENCES posts(id) ON DELETE CASCADE, + author REFERENCES authors(id) ON DELETE CASCADE, + isanon INTEGER, + comment_text TEXT, + hashedip BLOB, + post_time INTEGER + ); + ]])) + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + postid REFERENCES posts(id) ON DELETE CASCADE, + tag TEXT + ); + ]])) + assert(db:exec([[ + CREATE TABLE IF NOT EXISTS sessions ( + key TEXT PRIMARY KEY, + author REFERENCES authors(id) ON DELETE CASCADE, + start INTEGER + ); + ]])) + print("Created db tables") + assert(cache:exec([[ + CREATE TABLE IF NOT EXISTS cache ( + path TEXT PRIMARY KEY, + data BLOB, + updated INTEGER, + dirty INTEGER + ); + ]])) + stmnt_index = assert(db:prepare([[ + SELECT + posts.id, + posts.post_title, + posts.isanon, + posts.post_time, + authors.name + FROM + posts, + authors + WHERE + posts.authorid = authors.id + UNION + SELECT + posts.id, + posts.post_title, + posts.isanon, + posts.post_time, + 'Anonymous' + FROM + posts + WHERE + posts.authorid = -1 + ORDER BY + posts.post_time DESC + LIMIT 10; + ]])) + stmnt_read = assert(db:prepare([[ + SELECT + post_title, + post_text, + posts.authorid, + posts.isanon, + authors.name + FROM + posts,authors + WHERE + posts.authorid = authors.id AND + posts.id = :id; + ]])) + stmnt_author_bio = assert(db:prepare([[ + SELECT authors.biography FROM authors WHERE authors.name = :author; + ]])) + stmnt_author = assert(db:prepare([[ + SELECT + posts.id, + posts.post_title, + posts.post_time + FROM + posts, + authors + WHERE + posts.isanon = 0 AND + posts.authorid = authors.id AND + authors.name = :author + ORDER BY + posts.post_time DESC + LIMIT 10; + ]])) + stmnt_author_acct = assert(db:prepare([[ + SELECT id, salt, passhash FROM authors WHERE name = :name; + ]])) + stmnt_author_create = assert(db:prepare([[ + INSERT OR FAIL INTO authors ( + name, + salt, + passhash, + joindate, + biography + ) VALUES ( + :name, + :salt, + :hash, + strftime('%s','now'), + '' + ); + ]])) + stmnt_author_login = assert(db:prepare([[ + SELECT name, passhash FROM authors WHERE name = :name; + ]])) + stmnt_paste = assert(db:prepare([[ + INSERT INTO posts ( + post_text, + post_title, + authorid, + isanon, + hashedip, + post_time + ) VALUES ( + ?, + ?, + ?, + ?, + ?, + strftime('%s','now') + ); + ]])) + stmnt_raw = assert(db:prepare([[ + INSERT INTO raw_text ( + id, post_text, markup + ) VALUES ( + ?, ?, ? + ); + ]])) + stmnt_edit = assert(db:prepare([[ + SELECT + raw_text.post_text, raw_text.markup, posts.isanon + FROM + raw_text, posts + WHERE + raw_text.id = posts.id AND + raw_text.id = :postid; + ]])) + stmnt_insert_session = assert(db:prepare([[ + INSERT INTO sessions ( + key, + author, + start + ) VALUES ( + :sessionid, + :authorid, + strftime('%s','now') + ); + ]])) + stmnt_get_session = assert(db:prepare([[ + SELECT authors.name, authors.id + FROM authors, sessions + WHERE + sessions.key = :key AND + sessions.author = authors.id AND + sessions.start - strftime('%s','now') < 60*60*24; + ]])) + --only refresh pages at most once every 10 seconds + stmnt_cache = cache:prepare([[ + SELECT data + FROM cache + WHERE + path = :path AND + ((dirty = 0) OR (strftime('%s','now') - updated) < 10) + ; + ]]) + stmnt_insert_cache = cache:prepare([[ + INSERT OR REPLACE INTO cache ( + path, data, updated, dirty + ) VALUES ( + :path, :data, strftime('%s','now'), 0 + ); + ]]) + stmnt_dirty_cache = cache:prepare([[ + UPDATE OR IGNORE cache + SET dirty = 1 + WHERE path = :path; + ]]) + --[=[ + ]=] +end +print("Created configure function") + +--[[ +find a string url for a number +]] + +local function encode_id(number) + local result = {} + local charlen = string.len(url_characters) + repeat + local pos = (number % charlen) + 1 + number = math.floor(number / charlen) + table.insert(result,string.sub(url_characters,pos,pos)) + until number == 0 + return table.concat(result) +end + +local function decode_id(s) + local n = 0 + local charlen = string.len(url_characters) + for i = 1,string.len(s) do + local char = string.sub(s,i,i) + local pos = url_characters_rev[char] - 1 + n = n + (pos*math.pow(charlen,i-1)) + end + return n +end + +local function do_sql(stmnt) + if not stmnt then error("No statement",2) end + local err + repeat + err = stmnt:step() + print("After stepping, err is", err) + if err == sql.BUSY then + coroutine.yield() + end + until(err ~= sql.BUSY) + return err +end + +local function dirty_cache(url) + stmnt_dirty_cache:bind_names{ + path = string.format("%s/%s",domain,url) + } + err = do_sql(stmnt_dirty_cache) + stmnt_dirty_cache:reset() +end + + +--[[ +Start a session for someone who logged in +]] +local function start_session(who) + local rngf = assert(io.open("/dev/urandom","rb")) + local session_t = {} + for i = 1,64 do + local r = string.byte(rngf:read(1)) + local s = string.char((r % 26) + 65) + table.insert(session_t,s) + end + local session = table.concat(session_t) + rngf:close() + print("sessionid:",session) + print("authorid:",who) + stmnt_insert_session:bind_names{ + sessionid = session, + authorid = who + } + local err = do_sql(stmnt_insert_session) + print("Err:",err) + assert(err == sql.DONE) + return session +end +--[[ +Retreive the name and authorid of the logged in person, +or nil+error message if not logged in +]] +local function get_session(req) + http_populate_cookies(req) + local sessionid = http_request_cookie(req,"session") + if sessionid == nil then + return nil, "No session cookie passed by client" + end + stmnt_get_session:bind_names{ + key = sessionid + } + local err = do_sql(stmnt_get_session) + if err ~= sql.ROW then + return nil, "No such session by logged in users" + end + print("get session err:",err) + local data = stmnt_get_session:get_values() + stmnt_get_session:reset() + local author = data[1] + local authorid = data[2] + return author,authorid +end + +--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache() +local function render(pagename,callback) + print("Running render...") + stmnt_cache:bind_names{path=pagename} + local err = do_sql(stmnt_cache) + if err == sql.DONE then + stmnt_cache:reset() + --page is not cached + elseif err == sql.ROW then + print("Cache hit!") + data = stmnt_cache:get_values() + stmnt_cache:reset() + return data[1] + else --sql.ERROR or sql.MISUSE + error("Failed to check cache for page " .. pagename) + end + --We didn't have the paged cached, render it + print("Cache miss, running function") + local text = callback() + print("Saving data...") + --And save the data back into the cache + stmnt_insert_cache:bind_names{ + path=pagename, + data=text, + } + err = do_sql(stmnt_insert_cache) + if err == sql.ERROR or err == sql.MISUSE then + error("Failed to update cache for page " .. pagename) + end + stmnt_insert_cache:reset() + return text +end + +local function author_page(name) + +end + +function home(req) + print("Hello from lua!") + print("Method:", http_method_text(req)) + local method = http_method_text(req) + local host = http_request_get_host(req) + local path = http_request_get_path(req) + local text + if host == domain then + text = render(host..path,function() + print("Cache miss, rendering index") + stmnt_index:bind_names{} + local err = do_sql(stmnt_index) + print("err:",err) + local latest = {} + while err == sql.ROW do + local data = stmnt_index:get_values() + for k,v in pairs(data) do + print(k,":",v) + end + table.insert(latest,{ + url = encode_id(data[1]), + title = data[2], + isanon = data[3] == 1, + posted = os.date("%B %d %Y",tonumber(data[4])), + author = data[5], + }) + err = stmnt_index:step() + end + stmnt_index:reset() + --[[ + local latest = stmnt_index:get_values() + print("latest:",latest) + for k,v in pairs(latest) do + print(k,":",v) + end + ]] + print("returning...\n") + return pages.index{ + domain = domain, + stories = latest + } + end) + else + print("Author login") + local subdomain = host:match("([^\\.]+)") + text = render(host..path,function() + print("Cache miss, rendering author:" .. subdomain) + stmnt_author_bio:bind_names{author=subdomain} + local err = do_sql(stmnt_author_bio) + if err == sql.DONE then + print("No such author") + stmnt_author_bio:reset() + return pages.noauthor{ + author = subdomain + } + end + print("err:",err) + assert(err == sql.ROW,"failed to get author:" .. subdomain .. " error:" .. tostring(err)) + local data = stmnt_author_bio:get_values() + local bio = data[1] + stmnt_author_bio:reset() + print("Getting author's stories") + stmnt_author:bind_names{author=subdomain} + err = do_sql(stmnt_author) + print("err:",err) + local stories = {} + while err ~= sql.DONE do + local data = stmnt_author:get_values() + print("Added story:",data) + for k,v in pairs(data) do + print(k,":",v) + end + table.insert(stories,{ + url = encode_id(data[1]), + title = data[2], + posted = os.date("%B %d %Y",tonumber(data[3])) + }) + err = stmnt_author:step() + end + stmnt_author:reset() + return pages.author_index{ + domain=domain, + author=subdomain, + stories=stories, + bio=bio + } + end) + end + print("Host:",http_request_get_host(req)) + print("Path:",http_request_get_path(req)) + print("subdomain:",subdomain) + print("index:",pages.index) + --local text = pages.index({domain = domain}) + print("returning:",text) + assert(text) + http_response(req,200,text) +end + +--We prevent people from changing their password file, this way we don't really +--need to worry about logged in accounts being hijacked if someone gets at the +--database. The attacker can still paste from the logged in account for a while, +--but whatever. +function claim(req) + local method = http_method_text(req) + local host = http_request_get_host(req) + local path = http_request_get_path(req) + if host ~= domain then + http_response_header(req,"Location",string.format("https://%s/_claim",domain)) + http_response(req,303,"") + return + end + assert(host == domain) + local text + print("method:",method) + if method == "GET" then + print("render is:",render) + text = render(host..path,function() + print("cache miss, rendering claim page") + return pages.claim{} + end) + elseif method == "POST" then + http_request_populate_post(req) + local name = assert(http_argument_get_string(req,"user")) + local rngf = assert(io.open("/dev/urandom","rb")) + local passlength = string.byte(rngf:read(1)) + 64 + local salt = rngf:read(64) + local password = rngf:read(passlength) + rngf:close() + print("Starting session:",session) + print("About to xor") + print("About to hash") + local hash = sha3(salt .. password) + print("done hashing") + stmnt_author_create:bind_names{ + name = name, + } + stmnt_author_create:bind_blob(2,salt) + stmnt_author_create:bind_blob(3,hash) + print("Everything bound, ready to go") + local err = do_sql(stmnt_author_create) + print("Error:",err) + print("DONE",sql.DONE) + print("ERROR",sql.ERROR) + print("MISUSE",sql.MISUSE) + print("ROW",sql.ROW) + if err == sql.DONE then + print("New author:",name) + local id = stmnt_author_create:last_insert_rowid() + print("ID:",id) + stmnt_author_create:reset() + http_response_header(req,"Content-Type","application/octet-stream") + http_response_header(req,"Content-Disposition","attachment; filename=\"" .. domain .. "." .. name .. ".passfile\"") + local session = start_session(id) + + http_response_cookie(req,"session",session,"/",0,0) + text = password + elseif err == sql.CONSTRAINT then + text = pages.claim { + err = "Failed to claim. That name may already be taken." + } + elseif err == sql.ERROR or err == sql.MISUSE then + text = pages.claim { + err = "Failed to claim" + } + end + stmnt_author_create:reset() + end + assert(text) + http_response(req,200,text) +end + +function paste(req) + local host = http_request_get_host(req) + local path = http_request_get_path(req) + local method = http_method_text(req) + local err + local ret + if method == "GET" then + if host == domain then + ret = render(host..path,function() + print("Cache missing, rendering post page") + return pages.paste{ + domain = domain, + } + end) + else + print("Looks like a logged in user wants to paste!") + local subdomain = host:match("([^\\.]+)") + local author,_ = get_session(req) + if author == nil then + print("sessionid was nil") + http_response_header(req,"Location","https://"..domain.."/_paste") + http_response(req,303,"") + return + end + if author ~= subdomain then + http_response_header(req,"Location",string.format("https://%s.%s/_paste",author,domain)) + http_response(req,303,"") + return + end + assert(author == subdomain,"someone wants to paste as someone else") + ret = pages.author_paste{ + domain = domain, + user = author, + text = "", + } + end + elseif method == "POST" then + http_request_populate_post(req) + local title = assert(http_argument_get_string(req,"title")) + local text = assert(http_argument_get_string(req,"text")) + local markup = assert(http_argument_get_string(req,"markup")) + local pasteas + local raw = zlib.compress(text) + print("text1",text) + --text = string.gsub(text,"(%+)"," ") + print("text2",text) + text = string.gsub(text,"%%(%x%x)",decodeentities) + print("After decode:\n",text) + text = parsers[markup](text) + print("After markup:",text) + --text = string.gsub(text,escapematch,sanitize) + text = zlib.compress(text) + print("After deflate:",text) + print("inflating this data, we would get", zlib.decompress(text)) + local esctitle = string.gsub(title,"%%(%x%x)",decodeentities) + esctitle = parsers.plain(title) + print("title:",esctitle) + --TODO:paste to author page + if host == domain then + print("got text:",text) + --[[ + This doesn't actually do much for IPv4 addresses, + since there are only 32 bits of address, someone could + just generate all 2^32 hashes and look up who posted + what. Use IPv6, Tor or I2P where possible. (but then I + guess it's harder to ban spammers... hmm..) + ]] + --local ip = http_request_get_ip(req) + --local iphash = sha3(ip) + assert(stmnt_paste:bind_blob(1,text) == sql.OK) + assert(stmnt_paste:bind(2,esctitle) == sql.OK) + assert(stmnt_paste:bind(3,-1) == sql.OK) + assert(stmnt_paste:bind(4,true) == sql.OK) + assert(stmnt_paste:bind_blob(5,"") == sql.OK) + err = do_sql(stmnt_paste) + print("err:",err) + if err == sql.DONE then + local rowid = stmnt_paste:last_insert_rowid() + assert(stmnt_raw:bind(1,rowid) == sql.OK) + assert(stmnt_raw:bind_blob(2,raw) == sql.OK) + err = do_sql(stmnt_raw) + if err ~= sql.DONE then + print("Failed to save raw text, but paste still went though") + end + print("Successful paste, rowid:", rowid) + local url = encode_id(rowid) + local loc = string.format("https://%s/%s",domain,url) + http_response_header(req,"Location",loc) + http_response(req,303,"") + stmnt_paste:reset() + stmnt_raw:reset() + dirty_cache(string.format("%s/%s",domain,url)) + dirty_cache(string.format("%s",domain)) + return + elseif err == sql.ERROR or err == sql.MISUSE then + ret = "Failed to paste: " .. tostring(err) + else + error("Error pasting:",err) + end + stmnt_paste:reset() + + else + --local subdomain = host:match("([^\\.]+)") + --http_populate_cookies(req) + --local sessionid = http_request_cookie(req,"session") + --if sessionid == nil then --If someone not logged in tries to paste as someone else, send give them an error + --ret = pages.author_paste{ + --domain = domain, + --author = subdomain, + --err = "You are not logged in, you must be logged in to post as " .. subdomain .. ".", + --text = text + --} + --end + --print("Got cookie:",sessionid) + --stmnt_get_session:bind_names{ + --key = sessionid + --} + --err = do_sql(stmnt_get_session) + --print("err:",err) + --local data = stmnt_get_session:get_values() + --stmnt_get_session:reset() + --print("got data:",data) + --for k,v in pairs(data) do + --print(k,":",v) + --end + local author, authorid = get_session(req) + if author == nil then + ret = pages.author_paste{ + domain = domain, + author = subdomain, + err = "You are not logged in, you must be logged in to post as " .. subdomain .. ".", + text = text + } + end + + --local author = data[1] + --local authorid = data[2] + local asanon = assert(http_argument_get_string(req,"pasteas")) + --No need to check if the author is posting to the + --right sudomain, just post it to the one they have + --the key for. + assert(stmnt_paste:bind_blob(1,text) == sql.OK) + assert(stmnt_paste:bind(2,esctitle) == sql.OK) + assert(stmnt_paste:bind(3,authorid) == sql.OK) + if asanon == "anonymous" then + assert(stmnt_paste:bind(4,true) == sql.OK) + else + assert(stmnt_paste:bind(4,false) == sql.OK) + end + assert(stmnt_paste:bind_blob(5,"") == sql.OK) + err = do_sql(stmnt_paste) + if err == sql.DONE then + local rowid = stmnt_paste:last_insert_rowid() + assert(stmnt_raw:bind(1,rowid) == sql.OK) + assert(stmnt_raw:bind_blob(2,raw) == sql.OK) + assert(stmnt_raw:bind(3,markup) == sql.OK) + err = do_sql(stmnt_raw) + if err ~= sql.DONE then + print("Failed to save raw text, but paste still went through") + end + local url = encode_id(rowid) + local loc + if asanon == "anonymous" then + loc = string.format("https://%s/%s",domain,url) + else + loc = string.format("https://%s.%s/%s",author,domain,url) + end + http_response_header(req,"Location",loc) + http_response(req,303,"") + stmnt_paste:reset() + stmnt_raw:reset() + dirty_cache(string.format("%s/%s",domain,url)) + dirty_cache(string.format("%s",domain)) + return + elseif err == sql.ERROR or err == sql.MISUSE then + ret = "Failed to paste: " .. tostring(err) + else + error("Error pasting:",err) + end + stmnt_paste:reset() + end + end + assert(ret) + http_response(req,200,ret) +end + +function read(req) + local host = http_request_get_host(req) + local path = http_request_get_path(req) + print("host:",host) + print("path:",path) + local idp = string.sub(path,2)--remove leading "/" + assert(string.len(path) > 0,"Tried to read 0-length story id") + local author, authorid = get_session(req) + print("author is:",author) + local text + if author then + local id = decode_id(idp) + stmnt_read:bind_names{ + id = id + } + local err = do_sql(stmnt_read) + print("err:",err) + if err == sql.DONE then + stmnt_read:reset() + return pages.nostory{ + path = path + } + end + assert(err == sql.ROW) + local title, storytext, tauthor, isanon, authorname = unpack(stmnt_read:get_values()) + stmnt_read:reset() + if tauthor == authorid then + print("We're the owner of this story!") + local uncompressed = zlib.decompress(storytext) + text = pages.read{ + domain = domain, + title = title, + text = text, + idp = idp, + isanon = isanon == 1, + author = authorname, + owner = true + } + + else + print("We're logged in, but not the owner of this story!") + end + else + text = render(host..path,function() + print("Trying to read, id is",idp,":",decode_id(idp)) + local id = decode_id(idp) + print("id:",id,type(id)) + stmnt_read:bind_names{ + id = id + } + local err = do_sql(stmnt_read) + print("err:",err) + if err == sql.ROW then + + elseif err == sql.DONE then + stmnt_read:reset() + return pages.nostory{ + path = path + } + end + assert(err == sql.ROW,"Could not get row:" .. tostring(id) .. " Error:" .. tostring(err)) + print("get_values:") + local title, text, authorid, isanon, authorname = unpack(stmnt_read:get_values()) + print("Got text from unpack:",text) + text = zlib.decompress(text) + print("inflated text:",text) + print("title:",title) + print("text:",text) + print("idp:",idp) + stmnt_read:reset() + return pages.read{ + domain = domain, + title = title, + text = text, + idp = idp, + isanon = isanon == 1, + author = authorname + } + end) + end + assert(text) + http_response(req,200,text) +end + +function login(req) + print("Logging in") + local host = http_request_get_host(req) + local path = http_request_get_path(req) + local method = http_method_text(req) + if host ~= domain then + http_response_header(req,"Location",string.format("https://%s/_login",domain)) + http_response(req,303,"") + return + end + local text + if method == "GET" then + text = render(host..path,function() + return pages.login{} + end) + elseif method == "POST" then + --http_request_populate_post(req) + http_populate_multipart_form(req) + local name = assert(http_argument_get_string(req,"user")) + local pass = assert(http_file_get(req,"pass")) + print("name:",name) + print("pass:",pass) + stmnt_author_acct:bind_names{ + name = name + } + local err = do_sql(stmnt_author_acct) + print("err:",err) + if err == sql.ROW then + local id, salt, passhash = unpack(stmnt_author_acct:get_values()) + stmnt_author_acct:reset() + print("salt:",salt) + print("passhash:",passhash) + local todigest = salt .. pass + local hash = sha3(todigest) + print("hash:",hash) + print("passhash:",passhash) + if hash == passhash then + print("Passfile accepted") + local session = start_session(id) + http_response_cookie(req,"session",session,"/",0,0) + local loc = string.format("https://%s.%s",name,domain) + http_response_header(req,"Location",loc) + http_response(req,303,"") + return + else + text = pages.login{ + err = "Incorrect username or password" + } + end + elseif err == sql.DONE then --Allows user enumeration, do we want this? + --Probably not a problem since all passwords are forced to be "good" + text = pages.login{ + err = "Failed to find user:" .. name + } + else + error("Other sql error during login") + end + end + assert(text) + http_response(req,200,text) +end + +function edit(req) + local host = http_request_get_host(req) + local path = http_request_get_path(req) + local method = http_method_text(req) + if method == "GET" then + http_request_populate_qs(req) + local story = assert(http_argument_get_string(req,"story")) + local story_id = decode_id(story) + print("we want to edit story:",story) + stmnt_edit:bind_names{ + postid = story_id + } + local err = do_sql(stmnt_edit) + print("err:",err) + if err == sql.DONE then + print("No such story to edit:",story_id) + end + assert(err == sql.ROW) + local data = stmnt_edit:get_values() + local txt_compressed, markup, isanon = unpack(data) + for k,v in pairs(data) do + print(k,":",v) + end + elseif method == "POST" then + --TODO: same as paste? + --nope, need to replace the story instead of inserting a new one. + http_request_populate_post(req) + end +end + +function edit_bio() + print("we want to edit bio") +end + +function teardown() + print("Exiting...") + if db then + db:close() + end + if cache then + cache:close() + end + print("Finished lua teardown") +end + +print("Done with init.lua") +--[==[ +]==] diff --git a/src/lua/parser_imageboard.lua b/src/lua/parser_imageboard.lua new file mode 100644 index 0000000..ed94c61 --- /dev/null +++ b/src/lua/parser_imageboard.lua @@ -0,0 +1,111 @@ + +local lpeg = require("lpeg") +lpeg.locale(lpeg) +local V,P,C,S,B,Cs = lpeg.V,lpeg.P,lpeg.C,lpeg.S,lpeg.B,lpeg.Cs +--Characters to escape in the body text +local escapes = { + ["&"] = "&", + ["<"] = "<", + [">"] = ">", +} +local esctbl = {} +for char,_ in pairs(escapes) do + table.insert(esctbl,char) +end +local escapematch = string.format("([%s])",table.concat(esctbl)) +local function sanitize_item(capture) + return escapes[capture] or capture +end +local function sanitize(text) + local ret,_ = string.gsub(text,escapematch,sanitize_item) + return ret +end + +--Grammer +local space = S" \t\r"^0 +local special = P{ + P"**" + P"''" + P"'''" + + P"__" + P"==" + P"~~" + + P"\n>" + P"\n<" + P"\n" + + P"[code]" + P"[spoiler]" +} +local word = Cs((1 - special)^1) * space / sanitize + +--Generates a pattern that formats text inside matching 'seq' tags with format +--ex wrap("^^",[[%s]]) +--will wrapp text "5^^3^^" as "53" +local function wrap(seq,format) + return P(seq) * Cs(((1 - P(seq)) * space)^1) * P(seq) * space / function(a) + return string.format(format,sanitize(a)) + end +end + +--Generates a pattern that formats text inside openinig and closing "name" tags +--with a format, BB forum style +local function tag(name,format) + local start_tag = P(string.format("[%s]",name)) + local end_tag = P(string.format("[/%s]",name)) + return start_tag * Cs(((1 - end_tag) * space)^1) * end_tag * space / function(a) + return string.format(format,sanitize(a)) + end +end + +local grammer = P{ + "chunk"; + --regular + spoiler = wrap("**",[[%s]]), + spoiler2 = tag("spoiler",[[%s]]), + italic = wrap("''",[[%s]]), + bold = wrap("'''",[[%s]]), + underline = wrap("__",[[%s]]), + heading = wrap("==",[[

%s

]]), + strike = wrap("~~",[[%s]]), + code = tag("code",[[
%s
]]), + greentext = P"> " * Cs((V"marked" + word)^0) / function(a) + return string.format([[>%s]],a) + end, + pinktext = P"< " * Cs((V"marked" + word)^0) / function(a) + return string.format([[<%s]],a) + end, + marked = V"spoiler" + V"bold" + V"italic" + V"underline" + V"heading" + V"strike" + V"spoiler2" + V"code", + plainline = (V"marked" + word)^0, + line = Cs(V"greentext" + V"pinktext" + V"plainline" + P"") * P"\n" / function(a) + return string.format("%s
",a) + end, + ending = C(P(1)^0) / sanitize, + chunk = V"line"^0 * V"plainline" * V"ending" +} + +--local text = [[ +--this is **a big** test with ''italics''! +--we need to > sanitize < things that could be tags +--like really badly +--words can include any'single item without=penalty +--Can you use '''one tag ==within== another tag'''? +--let's see if [spoiler]spoiler tags work[/spoiler] +--things might even __go over +--multiple lines__ blah +--Let's test out those [code] +--code tag,s and see how well +--they work + --here's ome + --preformated + --text +--[/code] +--> Or have blank lines + +--one important thing is that greentext > should not start in the middle of a line +--> this next line is a green text, what if I include **markup** inside it? +--< and after '''it is''' a pinktext +--> because of some of these restrictions **bold text +--cannot go over multiple lines** in a green text +--__and finally__ there might be some text with ''' +--incomplete syntax with injection !!!! +--]] + +return function(text) + return table.concat({grammer:match(text .. "\n")}," ") +end +--for k,v in pairs({grammer:match(text)}) do + --print(k,":",v) +--end diff --git a/src/lua/parser_plain.lua b/src/lua/parser_plain.lua new file mode 100644 index 0000000..c047092 --- /dev/null +++ b/src/lua/parser_plain.lua @@ -0,0 +1,22 @@ +--Characters to escape in the body text +local escapes = { + ["&"] = "&", + ["<"] = "<", + [">"] = ">", + ['"'] = """, + ["'"] = "'", + --Kinda hacky + ["\n"] = "
", +} +local esctbl = {} +for char,_ in pairs(escapes) do + table.insert(esctbl,char) +end +local escapematch = string.format("([%s])",table.concat(esctbl)) + +local function sanitize(capture) + return escapes[capture] or capture +end +return function(text) + return string.gsub(text,escapematch,sanitize) +end diff --git a/src/pages/author_edit.etlua b/src/pages/author_edit.etlua new file mode 100644 index 0000000..42b04e4 --- /dev/null +++ b/src/pages/author_edit.etlua @@ -0,0 +1,42 @@ +<% assert(author,"No author specified") %> +<% assert(bio,"No bio included") %> + + + + + + 🍑 + + + + +

+ <%= author %>.<%= domain %> +

+ +
+
+
+ +
+
+
+ <% if #stories == 0 then %> + This author has not made any pastes yet. + <% else %> + + <% for k,v in pairs(stories) do %> + + <% end %> +
<%= v[1] %><%= v[2] %>
+ <% end %> +
+ +
+ +
+ + + diff --git a/src/pages/author_index.etlua b/src/pages/author_index.etlua new file mode 100644 index 0000000..a9689b5 --- /dev/null +++ b/src/pages/author_index.etlua @@ -0,0 +1,45 @@ +<% assert(author,"No author specified") %> +<% assert(bio,"No bio included") %> + + + + + + 🍑 + + + + +

+ <%= author %>.<%= domain %> +

+ +
+ <%= bio %> +
+
+ <% if #stories == 0 then %> + This author has not made any pastes yet. + <% else %> + + <% for k,v in pairs(stories) do %> + + <% end %> +
+ + <%- v.title %> + + + By <%= author %> + + <%= v.posted %> +
+ <% end %> +
+ +
+ +
+ + + diff --git a/src/pages/author_paste.etlua b/src/pages/author_paste.etlua new file mode 100644 index 0000000..e04c7ed --- /dev/null +++ b/src/pages/author_paste.etlua @@ -0,0 +1,42 @@ + + + + + + 🍑 + + + + +
+

+ Paste +

+ <% if err then %><%= err %><% end %> +
+
+
+ + + +
+
+
+
+ + +
+
+
+ +
+
+ + + diff --git a/src/pages/claim.etlua b/src/pages/claim.etlua new file mode 100644 index 0000000..73baee8 --- /dev/null +++ b/src/pages/claim.etlua @@ -0,0 +1,35 @@ + + + + + + 🍑 + + + + +
+

+ Register +

+ Once you press submit, you will be prompted to download a file.
+ slash.monster uses this file in place of a password, keep it safe.
+ Consider keeping a copy on a USB in case your hard drive fails.
+ The admin cannot recover your passfile, and will not reset accounts.
+ Names may be up to 30 characters, alphanumeric, all lower case.
+ <% if err then %><%= err %><% end %> +
+
+ + + +
+
+ Once you have your file, you can log in +
+ +
+
+ + + diff --git a/src/pages/edit.etlua b/src/pages/edit.etlua new file mode 100644 index 0000000..3814646 --- /dev/null +++ b/src/pages/edit.etlua @@ -0,0 +1,41 @@ + + + + + + 🍑 + + + + +
+

+ Paste +

+ <% if err then %><%= err %><% end %> +
+
+
+ + + +
+
+
+
+ + +
+
+
+ +
+
+ + + diff --git a/src/pages/index.etlua b/src/pages/index.etlua new file mode 100644 index 0000000..5d98c80 --- /dev/null +++ b/src/pages/index.etlua @@ -0,0 +1,49 @@ + + + + + 🍑 + + + + +
+

+ + <%= domain %> + +

+
+

+ Welcome to slash.monster, stories of fiction and fantasy
+ Not safe for work
+ 18+ +

+
+
+ <% if #stories == 0 then %> + No stories available. + <% else %> + + <% for k,v in pairs(stories) do %> + + <% end %> +
+ + <%- v.title %> + + + <% if v.isanon then %> + By Anonymous + <% else %> + By <%= v.author %> + <% end %> + + <%= v.posted %> +
+ <% end %> +
+
+ + + diff --git a/src/pages/login.etlua b/src/pages/login.etlua new file mode 100644 index 0000000..69ebd74 --- /dev/null +++ b/src/pages/login.etlua @@ -0,0 +1,31 @@ + + + + + + 🍑 + + + + +
+

+ Login +

+ <% if err then %><%= err %><% end %> +
+
+ + + + + +
+
+
+ +
+
+ + + diff --git a/src/pages/noauthor.etlua b/src/pages/noauthor.etlua new file mode 100644 index 0000000..90873cd --- /dev/null +++ b/src/pages/noauthor.etlua @@ -0,0 +1,22 @@ + + + + + 🙁 + + + + +
+

+ 🙁 +

+
+

+ No author found: <%= author %> +

+
+
+ + + diff --git a/src/pages/nostory.etlua b/src/pages/nostory.etlua new file mode 100644 index 0000000..8562b68 --- /dev/null +++ b/src/pages/nostory.etlua @@ -0,0 +1,22 @@ + + + + + 🙁 + + + + +
+

+ 🙁 +

+
+

+ No story found: <%= path %> +

+
+
+ + + diff --git a/src/pages/paste.etlua b/src/pages/paste.etlua new file mode 100644 index 0000000..7cf587b --- /dev/null +++ b/src/pages/paste.etlua @@ -0,0 +1,35 @@ + + + + + + 🍑 + + + + +
+

+ Paste +

+ <% if err then %><%= err %><% end %> +
+
+ + +
+
+
+
+ +
+
+ +
+
+ + + diff --git a/src/pages/read.etlua b/src/pages/read.etlua new file mode 100644 index 0000000..4c4f875 --- /dev/null +++ b/src/pages/read.etlua @@ -0,0 +1,34 @@ + + + + + <%= title %> + + + + +
+

+ <%= domain %>/<%= idp %> +

+ <% if owner then %> +
+ + +
+ <% end %> +

+ <%- title %> +

+ <% if isanon then %> + By Anonymous + <% else %> + By <%= author %> + <% end %> +
+ <%- text %> +
+
+ + + diff --git a/src/smr.c b/src/smr.c new file mode 100644 index 0000000..5935eaf --- /dev/null +++ b/src/smr.c @@ -0,0 +1,173 @@ +#include +#include +#ifdef BUILD_PROD +#include +#else +#define LUA_OK 0 +#endif +#include +#include +#include +#include "smr.h" +#include "libkore.h" +#include "libcrypto.h" +#include + +int home(struct http_request *); +int post_story(struct http_request *); +int edit_story(struct http_request *); +int edit_bio(struct http_request *); +int read_story(struct http_request *); +int login(struct http_request *); +int claim(struct http_request *); +int style(struct http_request *); +int miligram(struct http_request *); +int do_lua(struct http_request *req, const char *name); +int errhandeler(lua_State *); +lua_State *L; + +/* + static / index + static / _post post + static / _edit edit + dynamic / .* read + static / _login login + static / _claim claim +*/ + +int +errhandeler(lua_State *L){ + printf("Error: %s\n",lua_tostring(L,1)); + lua_getglobal(L,"debug"); + lua_getglobal(L,"print"); + lua_getfield(L,-2,"traceback"); + lua_call(L,0,1); + lua_call(L,1,0); + lua_pop(L,1); + return 1; +} + +int +do_lua(struct http_request *req, const char *name){ + printf("About to do lua %s\n",name); + lua_pushcfunction(L,errhandeler); + printf("Pushed errhandler function\n"); + lua_getglobal(L,name);//err(),name() + if(!lua_isfunction(L,-1)){ + printf("Could not find a method named %s\n",name); + lua_pop(L,2);// + return (KORE_RESULT_OK); + } + printf("About to push light userdata: %p\n",(void*)req); + lua_pushlightuserdata(L,req);//err,name(),ud_req + printf("About to pcall\n"); + int err = lua_pcall(L,1,0,-3); + if(err != LUA_OK){ + printf("Failed to run %s: %s\n",name,lua_tostring(L,-1)); + http_response(req, 500, NULL, 0); + lua_pop(L,lua_gettop(L)); + return (KORE_RESULT_OK); + } + return KORE_RESULT_OK; +} + +int +post_story(struct http_request *req){ + printf("We want to post!\n"); + return do_lua(req,"paste"); +} + +int +edit_story(struct http_request *req){ + printf("We want to edit!\n"); + return do_lua(req,"edit"); +} + +int +edit_bio(struct http_request *req){ + printf("We want to edit bio!\n"); + return do_lua(req,"edit_bio"); +} + +int +read_story(struct http_request *req){ + printf("We want to read!\n"); + return do_lua(req,"read"); +} + +int +login(struct http_request *req){ + printf("We want to login!\n"); + return do_lua(req,"login"); +} + +int +claim(struct http_request *req){ + printf("We want to claim!\n"); + return do_lua(req,"claim"); +} + +int +home(struct http_request *req){ + return do_lua(req,"home"); +} + +void +kore_worker_configure(void){ + printf("Configuring worker...\n"); + int err; + /*DIR *dp;*/ + /*struct dirent *ep;*/ + /*dp = opendir("./");*/ + /*if(dp != NULL){*/ + /*while(ep = readdir(dp)){*/ + /*printf("%s\n",ep->d_name);*/ + /*}*/ + /*closedir(dp);*/ + /*}*/ + L = luaL_newstate(); + luaL_openlibs(L); + load_kore_libs(L); + load_crypto_libs(L); + lua_pushcfunction(L,errhandeler); + printf("About to run loadfile...\n"); + luaL_loadfile(L,SM_INIT); + printf("Done running loadfile...\n"); + if(!lua_isfunction(L,-1)){//failed to loadfile() + printf("Failed to load %s: %s\n",SM_INIT,lua_tostring(L,-1)); + lua_pop(L,1); + return; + } + printf("About to pcall\n"); + err = lua_pcall(L,0,LUA_MULTRET,-2); + printf("Done pcalling\n"); + //err = luaL_dofile(L,"init.lua"); + if(err){ + printf("Failed to run %s\n",SM_INIT); + return; + } + lua_pushcfunction(L,errhandeler); + lua_getglobal(L,"configure"); + err = lua_pcall(L,0,0,-2); + if(err != LUA_OK){ + printf("Failed to run configure(): %s\n",lua_tostring(L,-1)); + lua_pop(L,2); + return; + } + lua_pop(L,lua_gettop(L)); + printf("Finished configuring worker\n"); +} + +void +kore_worker_teardown(void){ + int err; + lua_pushcfunction(L,errhandeler); + lua_getglobal(L,"teardown"); + err = lua_pcall(L,0,0,1); + if(err != LUA_OK){ + printf("Failed to run configure(): %s\n",lua_tostring(L,-1)); + lua_pop(L,2); + return; + } + lua_close(L); +} diff --git a/src/smr.h b/src/smr.h new file mode 100644 index 0000000..e2e029f --- /dev/null +++ b/src/smr.h @@ -0,0 +1 @@ +#define SM_INIT "init.lua"