scripts and tools to administer the lingy.in public unix / tilde
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

222 行
6.3KB

  1. import glob
  2. import json
  3. import re
  4. import sshpubkeys
  5. from flask import Flask, redirect, url_for, render_template, request
  6. # lyadmin
  7. # scripts and web form for a tilde / PAUS instance
  8. #
  9. # gashapwn
  10. # Nov 2020
  11. #
  12. # https://git.lain.church/gashapwn/lyadmin
  13. # gashapwn@protonmail.com
  14. # or
  15. # gasahwpn on irc.lainchan.org
  16. app=Flask(__name__)
  17. # Paths for conf file,
  18. # user list,
  19. # directory containing
  20. # account request files...
  21. WORKING_DIR = "/home/gashapwn/lyadmin/";
  22. ACCOUNT_DIR = "req/";
  23. FULL_PATH = str(WORKING_DIR) + str(ACCOUNT_DIR)
  24. CONF_PATH = str(WORKING_DIR) + "lyadmin.conf.json"
  25. MAX_PUB_KEY_LEN = 5000
  26. # Account requests are given ID numbers
  27. # the first request will have the below
  28. # id number
  29. INIT_REQ_ID = "00000"
  30. # Slurp the conf file
  31. with open(CONF_PATH) as c: conf_json_str = c.read()
  32. conf_obj = json.loads(conf_json_str)
  33. # A list of all the shell enums
  34. SHELL_TUP_LIST = list(map(
  35. lambda k : (
  36. k, conf_obj["shell"][k]
  37. ),
  38. list(conf_obj["shell"].keys())
  39. ))
  40. # The main home page
  41. @app.route("/")
  42. def home():
  43. app.route('/')
  44. # Load the list of tilde users
  45. # to generate links for
  46. u_list = [];
  47. with open("user_list.txt") as u_file:
  48. for line in u_file.readlines():
  49. u_list.append(line.strip());
  50. return render_template("index.html", u_list=u_list, page_name="home")
  51. # The page with rules
  52. def rules():
  53. return render_template("rules.html")
  54. # Generate HTML for a form widget
  55. def widg_fun(widg):
  56. if(widg.w_type == "input"):
  57. # Return HTML for a single line input
  58. return "input id=id_%s name=%s type=text></input"%(widg.w_name, widg.w_name)
  59. elif(widg.w_type == "textarea"):
  60. # Return HTML for a big text input box
  61. return "textarea cols=40 id=id_%s name=%s rows=10 required=\"\""%(widg.w_name, widg.w_name)
  62. elif(widg.w_type == "check"):
  63. # Return HTML for a check box
  64. return "input id=id_%s name=%s type=checkbox required=\"\""%(widg.w_name, widg.w_name)
  65. return widg.w_type;
  66. # Generate HTML for request form
  67. # probably a strange way to do this...
  68. def req():
  69. app.route('/req')
  70. class Widg:
  71. def __init__(self, w_name, w_type, w_opt):
  72. self.w_name = w_name
  73. self.w_type = w_type
  74. self.w_opt = w_opt # only for choice type widg
  75. # Configuration for our request form
  76. rt = {
  77. "username": Widg(
  78. "username",
  79. "input",
  80. None
  81. ),
  82. "email for account lockout / registration confirmation (optional)": Widg(
  83. "email",
  84. "input",
  85. None
  86. ),
  87. "SSH public key": Widg(
  88. "pub_key",
  89. "textarea",
  90. None
  91. ),
  92. "shell of choice": Widg(
  93. "shell",
  94. "choice",
  95. SHELL_TUP_LIST
  96. ),
  97. "have you read the rules?": Widg(
  98. "rule_read", "check", None
  99. )
  100. };
  101. return render_template("req.html", req_tab = rt, widg_fun = widg_fun, page_name="req")
  102. def handle_invalid_data(req):
  103. # print(str(e))
  104. return render_template("signup.html", is_email_user = False)
  105. # Process input from the /req page
  106. def signup():
  107. app.route('/req/signup')
  108. # Get all the params from the POST
  109. # request
  110. username = request.form["username"].strip()
  111. email = request.form["email"].strip()
  112. pub_key = request.form["pub_key"].strip()
  113. shell = request.form["shell"].strip()
  114. rule_read = request.form["rule_read"].strip()
  115. is_email_user = False;
  116. # If a user didnt read the rules
  117. # send them back
  118. # Browser validations should
  119. # prevent this....
  120. if(rule_read != "on"):
  121. return redirect(url_for('req'))
  122. # Set placeholder if user didnt send an email
  123. if(len(email) > 1):
  124. is_email_user = True
  125. else:
  126. email = "NO_EMAIL"
  127. # Validate shell
  128. if(not shell in conf_obj["shell"]):
  129. print("failed shell validation")
  130. return handle_invalid_data(req)
  131. # Validate email
  132. if( not re.search("^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,10}$", email)):
  133. print("failed email validation")
  134. return handle_invalid_data(req)
  135. # Validate the SSH pub key
  136. # Most software only handles up to 4096 bit keys
  137. if(len(pub_key) > MAX_PUB_KEY_LEN):
  138. print("key failed len check")
  139. return handle_invalid_data(req)
  140. # Only printable ascii characters in
  141. # a valid key
  142. if(not re.search("^[ -~]+$", pub_key)):
  143. print("key failed regex")
  144. return handle_invalid_data(req)
  145. # Check the key against a library
  146. key = sshpubkeys.SSHKey(pub_key, strict_mode=False, skip_option_parsing=True)
  147. try:
  148. key.parse()
  149. except Exception as e:
  150. print("key failed lib validation")
  151. return handle_invalid_data(request)
  152. # All users requests have a sequential ID
  153. # this checks how many requests we have
  154. # and gives us a free ID so we can save
  155. # our request
  156. # This sets the ID of the request we're
  157. # abou to save to dsik
  158. if(len(glob.glob(ACCOUNT_DIR + str("[0-9]*ident*"))) == 0):
  159. new_id = int(INIT_REQ_ID)
  160. new_id_str = INIT_REQ_ID
  161. else:
  162. max_id = max(list(map( lambda path : path.split("/")[-1].split(".")[0] , glob.glob(str(ACCOUNT_DIR) + "[0-9]*ident*"))))
  163. # max_id = max(list(map( lambda path : path.split("/")[-1].split(".")[0] , glob.glob("./test/[0-9]*ident*"))))
  164. zpad = len(max_id)
  165. new_id = int(max_id)+1
  166. new_id_str = str(new_id).zfill(zpad)
  167. # write the request to disk
  168. fn1 = str(FULL_PATH) + str(new_id_str) + ".ident"
  169. with open(fn1, "w") as ident_file:
  170. ident_file.write(str(username) + "\n")
  171. ident_file.write(str(email) + "\n")
  172. ident_file.write(str(shell) + "\n")
  173. ident_file.write(str(pub_key) + "\n")
  174. return render_template("signup.html", is_email_user = is_email_user)
  175. @app.context_processor
  176. def get_site_name():
  177. return {"site_name": conf_obj["site_name"]}
  178. if __name__=="__main__":
  179. app.add_url_rule('/rules', 'rules', rules)
  180. app.add_url_rule('/req', 'req', req, methods = ['POST', 'GET'])
  181. app.add_url_rule('/req/signup', 'signup', signup, methods = ['POST'])
  182. app.run(host=conf_obj["listen_ip"],debug=True)