def list_bulk_add(list_address): global_admin, allow_msg = allowed_access( check_is_global_admin=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=list_address) if not global_admin and not board_list_admin: return allow_msg form_list = forms_board.List() chan = Chan.query.filter(Chan.address == list_address).first() if not chan: return render_template("pages/404-board.html", board_address=list_address) try: from_list = daemon_com.get_from_list(list_address) except: return render_template("pages/404-board.html", board_address=list_address) board = {"current_chan": chan} status_msg = {"status_message": []} url = "" url_text = "" form_list_add = [] try: this_chan_list = json.loads(chan.list) except: this_chan_list = {} chans = Chan.query.filter(and_( Chan.address != list_address, Chan.address.notin_(this_chan_list))).order_by( Chan.type.asc(), Chan.label.asc()).all() for each_chan in chans: str_select = "" if each_chan.type == "board": str_select += "Board: " elif each_chan.type == "list": str_select += "List: " str_select += each_chan.label if each_chan.access == "public": str_select += " [Public] " elif each_chan.access == "private": str_select += " [Private] " str_select += "({}...{})".format( each_chan.address[:9], each_chan.address[-6:]) form_list_add.append((each_chan.address, str_select)) if request.method == 'GET': if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': add_bulk_list = [] for each_input in request.form: if each_input == "add_bulk": for each_input in request.form: if each_input.startswith("add_bulk_"): add_bulk_list.append(each_input.split("_")[2]) break # Add boards or lists to a list in bulk if form_list.add_bulk.data and add_bulk_list: if not global_admin and not board_list_admin: return allow_msg mod_list = Chan.query.filter(and_( Chan.type == "list", Chan.address == list_address)).first() dict_list_addresses = {} rules = {} if not mod_list: status_msg["status_message"].append("Invalid list to modify") else: if form_list.from_address.data: mod_list.default_from_address = form_list.from_address.data try: dict_list_addresses = json.loads(mod_list.list) except: pass try: rules = json.loads(mod_list.rules) except: pass for each_address in add_bulk_list: chan_add = Chan.query.filter(Chan.address == each_address).first() if not chan_add: status_msg["status_message"].append( "Can't find board/list to add: {}".format(each_address)) continue if form_list.add.data and each_address in dict_list_addresses: status_msg["status_message"].append( "Can't add address that's already on the list: {}".format(each_address)) if each_address == list_address: status_msg["status_message"].append( "Cannot modify an address that's the same address as the list: {}".format(each_address)) def sender_has_access(each_address, address_type): access = get_access(each_address) for each_address in daemon_com.get_identities(): if each_address in access[address_type]: return True for each_address in daemon_com.get_all_chans(): if each_address in access[address_type]: return True if mod_list.access == "private": if (sender_has_access(list_address, "primary_addresses") or sender_has_access(list_address, "secondary_addresses")): # Primary and secondary access can add or delete from lists modify_access = True elif (form_list.add.data and sender_has_access(list_address, "tertiary_addresses")): # Only allow tertiary access to add to private lists modify_access = True else: # Everyone else is prohibited from adding/deleting from private lists modify_access = False if not modify_access: status_msg["status_message"].append( "Cannot modify this list if you are not the owner.") errors, dict_chan_info = process_passphrase(chan_add.passphrase) if not dict_chan_info: status_msg['status_message'].append( "Error parsing passphrase for address {}".format(each_address)) if "allow_list_pgp_metadata" in rules and rules["allow_list_pgp_metadata"]: if dict_chan_info["type"] in ["board", "list"]: if len(chan_add.pgp_passphrase_msg) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Message PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_msg))) elif not chan_add.pgp_passphrase_msg: status_msg['status_message'].append( "Message PGP Passphrase of the entry you tried to add cannot be empty") if dict_chan_info["type"] == "board": if len(chan_add.pgp_passphrase_attach) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Attachment PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_attach))) elif not chan_add.pgp_passphrase_attach: status_msg['status_message'].append( "Attachment PGP Passphrase of the entry you tried to add cannot be empty") if len(chan_add.pgp_passphrase_steg) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Steg PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_steg))) elif not chan_add.pgp_passphrase_steg: status_msg['status_message'].append( "Steg PGP Passphrase of the entry you tried to add cannot be empty") dict_list_addresses[chan_add.address] = { "passphrase": chan_add.passphrase } if "allow_list_pgp_metadata" in rules and rules["allow_list_pgp_metadata"]: if dict_chan_info["type"] in ["board", "list"]: dict_list_addresses[chan_add.address]["pgp_passphrase_msg"] = chan_add.pgp_passphrase_msg if dict_chan_info["type"] == "board": dict_list_addresses[chan_add.address]["pgp_passphrase_attach"] = chan_add.pgp_passphrase_attach dict_list_addresses[chan_add.address]["pgp_passphrase_steg"] = chan_add.pgp_passphrase_steg if not status_msg['status_message']: status_msg["status_message"].append( "Added to the List: {}".format(", ".join(add_bulk_list))) status_msg['status_title'] = "Success" url = "/list/{}".format(list_address) url_text = "Return to List" # Set the time the list changed if mod_list.list != json.dumps(dict_list_addresses): mod_list.list_timestamp_changed = time.time() mod_list.list = json.dumps(dict_list_addresses) mod_list.list_send = True mod_list.save() add_mod_log_entry( "Locally Added to List: {}".format(", ".join(add_bulk_list)), message_id=None, user_from=None, board_address=list_address, thread_hash=None) logger.info("Instructing send_lists() to run in {} minutes".format( config.LIST_ADD_WAIT_TO_SEND_SEC / 60)) daemon_com.update_timer_send_lists(config.LIST_ADD_WAIT_TO_SEND_SEC) return render_template("pages/alert.html", board=list_address, status_msg=status_msg, url=url, url_text=url_text) time.sleep(3) session['status_msg'] = status_msg if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" chan_posts = Chan.query.filter(Chan.type == "board").all() chan_lists = {} for each_chan in Chan.query.filter(Chan.type == "list").all(): chan_lists[each_chan.address] = { "passphrase": each_chan.passphrase, "list": json.loads(each_chan.list) } if len(each_chan.label) > config.LABEL_LENGTH: chan_lists[each_chan.address]["label_short"] = each_chan.label[:config.LABEL_LENGTH] else: chan_lists[each_chan.address]["label_short"] = each_chan.label chan = Chan.query.filter(Chan.address == list_address).first() dict_join = { "passphrase": chan.passphrase } passphrase_base64 = base64.b64encode( json.dumps(dict_join).encode()).decode().replace("/", "&") if chan.pgp_passphrase_msg != config.PGP_PASSPHRASE_MSG: dict_join["pgp_msg"] = chan.pgp_passphrase_msg passphrase_base64_with_pgp = base64.b64encode( json.dumps(dict_join).encode()).decode().replace("/", "&") return render_template("pages/list_bulk_add.html", board=board, chan_lists=chan_lists, chan_posts=chan_posts, form_list=form_list, form_list_add=form_list_add, from_list=from_list, passphrase_base64=passphrase_base64, passphrase_base64_with_pgp=passphrase_base64_with_pgp, status_msg=status_msg, table_chan=Chan, url=url, url_text=url_text)
def join_from_list(list_address, join_address): global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg form_join = forms_board.Join() status_msg = {"status_message": []} url = "" url_text = "" chan_list = Chan.query.filter(and_( Chan.type == "list", Chan.address == list_address)).first() try: dict_list_addresses = json.loads(chan_list.list) except: dict_list_addresses = {} try: rules = json.loads(chan_list.rules) except: rules = {} if join_address not in dict_list_addresses: status_msg['status_message'].append("Address to join not in list") dict_chan_info = {} passphrase = "" if "passphrase" in dict_list_addresses[join_address]: passphrase = dict_list_addresses[join_address]["passphrase"] if Chan.query.filter(Chan.passphrase == passphrase).count(): status_msg['status_message'].append("Chan already in database") errors, dict_chan_info = process_passphrase(passphrase) if not dict_chan_info: status_msg['status_message'].append("Error parsing passphrase") for error in errors: status_msg['status_message'].append(error) if request.method == 'GET': if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': if form_join.join.data: # Join from list if dict_chan_info and passphrase and not status_msg['status_message']: url = None url_text = None if dict_chan_info["rules"]: dict_chan_info["rules"] = set_clear_time_to_future(dict_chan_info["rules"]) new_chan = Chan() new_chan.passphrase = passphrase new_chan.access = dict_chan_info["access"] new_chan.type = dict_chan_info["type"] new_chan.primary_addresses = json.dumps(dict_chan_info["primary_addresses"]) new_chan.secondary_addresses = json.dumps(dict_chan_info["secondary_addresses"]) new_chan.tertiary_addresses = json.dumps(dict_chan_info["tertiary_addresses"]) new_chan.rules = json.dumps(dict_chan_info["rules"]) new_chan.label = dict_chan_info["label"] new_chan.description = dict_chan_info["description"] if form_join.pgp_passphrase_msg.data: new_chan.pgp_passphrase_msg = form_join.pgp_passphrase_msg.data else: new_chan.pgp_passphrase_msg = config.PGP_PASSPHRASE_MSG if new_chan.type == "board": if form_join.pgp_passphrase_steg.data: new_chan.pgp_passphrase_steg = form_join.pgp_passphrase_steg.data else: new_chan.pgp_passphrase_steg = config.PGP_PASSPHRASE_STEG if form_join.pgp_passphrase_attach.data: new_chan.pgp_passphrase_attach = form_join.pgp_passphrase_attach.data else: new_chan.pgp_passphrase_attach = config.PGP_PASSPHRASE_ATTACH result = daemon_com.join_chan(passphrase, clear_inventory=form_join.resync.data) if result.startswith("BM-"): new_chan.address = result new_chan.is_setup = True if new_chan.type == "board": status_msg['status_message'].append("Joined board") url = "/board/{}/1".format(result) url_text = "{} - {}".format(new_chan.label, result) elif new_chan.type == "list": status_msg['status_message'].append("Joined list") url = "/list/{}".format(result) url_text = "{} - {}".format(new_chan.label, result) else: status_msg['status_message'].append("Could not join at this time: {}".format(result)) new_chan.address = None new_chan.is_setup = False if 'status_title' not in status_msg: status_msg['status_title'] = "Success" status_msg['status_message'].append(result) new_chan.save() return render_template("pages/alert.html", board=list_address, status_msg=status_msg, url=url, url_text=url_text) time.sleep(3) session['status_msg'] = status_msg if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return render_template("pages/list_join.html", dict_chan_info=dict_chan_info, dict_list_addresses=dict_list_addresses, form_join=form_join, join_address=join_address, rules=rules, status_msg=status_msg, url=url, url_text=url_text)
def list_chans(list_address): allowed, allow_msg = allowed_access(check_can_view=True) if not allowed: return allow_msg form_list = forms_board.List() form_set = forms_board.SetChan() chan = Chan.query.filter(Chan.address == list_address).first() if not chan: return render_template("pages/404-board.html", board_address=list_address) try: from_list = daemon_com.get_from_list(list_address) except: return render_template("pages/404-board.html", board_address=list_address) board = {"current_chan": chan} status_msg = {"status_message": []} url = "" url_text = "" form_list_add = [] try: this_chan_list = json.loads(chan.list) except: this_chan_list = {} chans = Chan.query.filter(and_( Chan.address != list_address, Chan.address.notin_(this_chan_list))).order_by( Chan.type.asc(), Chan.label.asc()).all() for each_chan in chans: str_select = "" if each_chan.type == "board": str_select += "Board: " elif each_chan.type == "list": str_select += "List: " str_select += each_chan.label if each_chan.access == "public": str_select += " [Public] " elif each_chan.access == "private": str_select += " [Private] " str_select += "({}...{})".format( each_chan.address[:9], each_chan.address[-6:]) form_list_add.append((each_chan.address, str_select)) if request.method == 'GET': if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg join_bulk = None join_bulk_list = [] join = None delete = None for each_input in request.form: if each_input.startswith("join_"): join = each_input.split("_")[1] break elif each_input.startswith("delete_"): delete = each_input.split("_")[1] break elif each_input == "joinbulk": join_bulk = True break if join_bulk: for each_input in request.form: if each_input.startswith("joinbulk_"): join_bulk_list.append(each_input.split("_")[1]) if form_set.set_pgp_passphrase_msg.data: global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg if not form_set.pgp_passphrase_msg.data: status_msg['status_message'].append("Message PGP passphrase required") elif len(form_set.pgp_passphrase_msg.data) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append("Message PGP passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(form_set.pgp_passphrase_msg.data))) else: chan.pgp_passphrase_msg = form_set.pgp_passphrase_msg.data chan.save() status_msg['status_title'] = "Success" status_msg['status_message'].append("Changed PGP Passphrase.") # set default/preferred address to update list elif form_list.save_from.data: global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg chan = Chan.query.filter( Chan.address == list_address).first() if chan: if form_list.from_address.data: chan.default_from_address = form_list.from_address.data else: chan.default_from_address = None chan.save() # Add/delete a board or list to/from a list elif form_list.add.data or delete: global_admin, allow_msg = allowed_access( check_is_global_admin=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=list_address) if not global_admin and not board_list_admin: return allow_msg address = None if form_list.add.data: address = form_list.address.data elif delete: address = delete chan_add = Chan.query.filter(Chan.address == address).first() if form_list.add.data and not chan_add: status_msg["status_message"].append("Invalid list to modify") else: mod_list = Chan.query.filter(and_( Chan.type == "list", Chan.address == list_address)).first() try: dict_list_addresses = json.loads(mod_list.list) except: dict_list_addresses = {} try: rules = json.loads(mod_list.rules) except: rules = {} if form_list.add.data and address in dict_list_addresses: status_msg["status_message"].append("Can't add address that's already on the list") if form_list.delete.data and address not in dict_list_addresses: status_msg["status_message"].append("Can't delete address that's not on the list") if address == list_address: status_msg["status_message"].append("Cannot modify an address that's the same address as the list") def sender_has_access(address, address_type): access = get_access(address) for each_address in daemon_com.get_identities(): if each_address in access[address_type]: return True for each_address in daemon_com.get_all_chans(): if each_address in access[address_type]: return True if mod_list.access == "private": if (sender_has_access(list_address, "primary_addresses") or sender_has_access(list_address, "secondary_addresses")): # Primary and secondary access can add or delete from lists modify_access = True elif (form_list.add.data and sender_has_access(list_address, "tertiary_addresses")): # Only allow tertiary access to add to private lists modify_access = True else: # Everyone else is prohibited from adding/deleting from private lists modify_access = False if not modify_access: status_msg["status_message"].append( "Cannot modify this list if you are not the owner.") # Check if passphrase is valid if form_list.add.data: errors, dict_chan_info = process_passphrase(chan_add.passphrase) if not dict_chan_info: status_msg['status_message'].append("Error parsing passphrase") if "allow_list_pgp_metadata" in rules and rules["allow_list_pgp_metadata"]: if dict_chan_info["type"] in ["board", "list"]: if len(chan_add.pgp_passphrase_msg) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Message PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_msg))) elif not chan_add.pgp_passphrase_msg: status_msg['status_message'].append( "Message PGP Passphrase of the entry you tried to add cannot be empty") if dict_chan_info["type"] == "board": if len(chan_add.pgp_passphrase_attach) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Attachment PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_attach))) elif not chan_add.pgp_passphrase_attach: status_msg['status_message'].append( "Attachment PGP Passphrase of the entry you tried to add cannot be empty") if len(chan_add.pgp_passphrase_steg) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Steg PGP Passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(chan_add.pgp_passphrase_steg))) elif not chan_add.pgp_passphrase_steg: status_msg['status_message'].append( "Steg PGP Passphrase of the entry you tried to add cannot be empty") if not status_msg['status_message']: status_msg['status_title'] = "Success" if form_list.add.data: dict_list_addresses[chan_add.address] = { "passphrase": chan_add.passphrase } if "allow_list_pgp_metadata" in rules and rules["allow_list_pgp_metadata"]: if dict_chan_info["type"] in ["board", "list"]: dict_list_addresses[chan_add.address]["pgp_passphrase_msg"] = chan_add.pgp_passphrase_msg if dict_chan_info["type"] == "board": dict_list_addresses[chan_add.address]["pgp_passphrase_attach"] = chan_add.pgp_passphrase_attach dict_list_addresses[chan_add.address]["pgp_passphrase_steg"] = chan_add.pgp_passphrase_steg status_msg["status_message"].append( "Added {} to the List".format(address)) add_mod_log_entry( "Locally Added to List: {}".format(address), message_id=None, user_from=None, board_address=list_address, thread_hash=None) elif form_list.delete.data: dict_list_addresses.pop(address) status_msg["status_message"].append( "Deleted {} from the List".format(address)) add_mod_log_entry( "Locally Deleted from List: {}".format(address), message_id=None, user_from=None, board_address=list_address, thread_hash=None) # Set the time the list changed if mod_list.list != json.dumps(dict_list_addresses): mod_list.list_timestamp_changed = time.time() mod_list.list = json.dumps(dict_list_addresses) mod_list.list_send = True mod_list.save() time_to_send = 60 * 1 logger.info("Instructing send_lists() to run in {} minutes".format(time_to_send / 60)) daemon_com.update_timer_send_lists(time_to_send) elif join: # Join from list global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg return redirect(url_for("routes_list.join_from_list", list_address=list_address, join_address=join)) elif join_bulk: # Bulk join from list global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg if not join_bulk_list: status_msg['status_title'] = "Error" status_msg["status_message"].append("You must check at least one list entry to join") else: daemon_com.bulk_join(list_address, join_bulk_list) status_msg['status_title'] = "Success" status_msg["status_message"].append( "Addresses being joined in the background. " "Give the process time to complete.") time.sleep(3) session['status_msg'] = status_msg if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" chan_posts = Chan.query.filter(Chan.type == "board").all() chan_lists = {} for each_chan in Chan.query.filter(Chan.type == "list").all(): chan_lists[each_chan.address] = { "passphrase": each_chan.passphrase, "list": json.loads(each_chan.list) } if len(each_chan.label) > config.LABEL_LENGTH: chan_lists[each_chan.address]["label_short"] = each_chan.label[:config.LABEL_LENGTH] else: chan_lists[each_chan.address]["label_short"] = each_chan.label chan = Chan.query.filter(Chan.address == list_address).first() dict_join = { "passphrase": chan.passphrase } passphrase_base64 = base64.b64encode( json.dumps(dict_join).encode()).decode().replace("/", "&") if chan.pgp_passphrase_msg != config.PGP_PASSPHRASE_MSG: dict_join["pgp_msg"] = chan.pgp_passphrase_msg passphrase_base64_with_pgp = base64.b64encode( json.dumps(dict_join).encode()).decode().replace("/", "&") return render_template("pages/list.html", board=board, chan_lists=chan_lists, chan_posts=chan_posts, form_list=form_list, form_list_add=form_list_add, from_list=from_list, passphrase_base64=passphrase_base64, passphrase_base64_with_pgp=passphrase_base64_with_pgp, status_msg=status_msg, url=url, url_text=url_text)
def join_base64(passphrase_base64): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg pgp_passphrase_msg = config.PGP_PASSPHRASE_MSG pgp_passphrase_attach = config.PGP_PASSPHRASE_ATTACH pgp_passphrase_steg = config.PGP_PASSPHRASE_STEG dict_chan_info = None status_msg = {"status_message": []} url = "" url_text = "" chan_exists = None stage = "join_passphrase" form_join = forms_board.Join() try: passphrase_dict_json = base64.b64decode( passphrase_base64.replace("&", "/")).decode() passphrase_dict = json.loads(passphrase_dict_json) passphrase_json = passphrase_dict["passphrase"] if "pgp_msg" in passphrase_dict: pgp_passphrase_msg = passphrase_dict["pgp_msg"] if "pgp_attach" in passphrase_dict: pgp_passphrase_attach = passphrase_dict["pgp_attach"] if "pgp_steg" in passphrase_dict: pgp_passphrase_steg = passphrase_dict["pgp_steg"] chan_exists = Chan.query.filter( Chan.passphrase == passphrase_json).first() errors, dict_chan_info = process_passphrase(passphrase_json) if not dict_chan_info: status_msg['status_message'].append("Error parsing passphrase") for error in errors: status_msg['status_message'].append(error) except Exception as err: status_msg['status_message'].append( "Issue parsing base64 string: {}".format(err)) if request.method == 'POST': if form_join.join.data: if not status_msg['status_message']: for each_word in RESTRICTED_WORDS: if each_word in dict_chan_info["label"].lower(): status_msg['status_message'].append( "bitchan is a restricted word for labels") if not status_msg['status_message']: result = daemon_com.join_chan( passphrase_json, clear_inventory=form_join.resync.data) if dict_chan_info["rules"]: dict_chan_info["rules"] = set_clear_time_to_future( dict_chan_info["rules"]) if form_join.pgp_passphrase_msg.data: pgp_passphrase_msg = form_join.pgp_passphrase_msg.data if form_join.pgp_passphrase_attach.data: pgp_passphrase_attach = form_join.pgp_passphrase_attach.data if form_join.pgp_passphrase_steg.data: pgp_passphrase_steg = form_join.pgp_passphrase_steg.data new_chan = Chan() new_chan.passphrase = passphrase_json new_chan.access = dict_chan_info["access"] new_chan.type = dict_chan_info["type"] new_chan.label = dict_chan_info["label"] new_chan.description = dict_chan_info["description"] new_chan.pgp_passphrase_msg = pgp_passphrase_msg if dict_chan_info["type"] == "board": new_chan.pgp_passphrase_attach = pgp_passphrase_attach new_chan.pgp_passphrase_steg = pgp_passphrase_steg new_chan.primary_addresses = json.dumps( dict_chan_info["primary_addresses"]) new_chan.secondary_addresses = json.dumps( dict_chan_info["secondary_addresses"]) new_chan.tertiary_addresses = json.dumps( dict_chan_info["tertiary_addresses"]) new_chan.restricted_addresses = json.dumps( dict_chan_info["restricted_addresses"]) new_chan.rules = json.dumps(dict_chan_info["rules"]) log_description = None if result.startswith("BM-"): new_chan.address = result new_chan.is_setup = True if new_chan.type == "board": status_msg['status_message'].append("Joined board") url = "/board/{}/1".format(result) url_text = "/{}/ - {}".format(new_chan.label, new_chan.description) log_description = "Joined board {} ({})".format( url_text, result) elif new_chan.type == "list": status_msg['status_message'].append("Joined list") url = "/list/{}".format(result) url_text = "{} - {}".format(new_chan.label, new_chan.description) log_description = "Joined list {} ({})".format( url_text, result) else: status_msg['status_message'].append("Creation queued") new_chan.address = None new_chan.is_setup = False if log_description: add_mod_log_entry(log_description, message_id=None, user_from=None, board_address=result, thread_hash=None) if 'status_title' not in status_msg: status_msg['status_title'] = "Success" new_chan.save() stage = "end" if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return render_template("pages/join.html", chan_exists=chan_exists, dict_chan_info=dict_chan_info, form_join=form_join, passphrase=passphrase_json, pgp_passphrase_msg=pgp_passphrase_msg, pgp_passphrase_attach=pgp_passphrase_attach, pgp_passphrase_steg=pgp_passphrase_steg, stage=stage, status_msg=status_msg, url=url, url_text=url_text)
def join(): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg form_join = forms_board.Join() status_msg = {"status_message": []} url = "" url_text = "" if not form_join.stage.data: stage = "start" else: stage = form_join.stage.data if request.method == 'POST': if stage == "start": if form_join.join_type.data == "join": stage = "join" elif form_join.join_type.data == "public_board": stage = "public_board" elif form_join.join_type.data == "private_board": stage = "private_board" elif form_join.join_type.data == "public_list": stage = "public_list" elif form_join.join_type.data == "private_list": stage = "private_list" # Join board or list elif stage == "join" and form_join.join.data: if not form_join.passphrase.data: status_msg['status_message'].append("Passphrase required") passphrase = None try: passphrase_dict = json.loads(form_join.passphrase.data) passphrase = json.dumps(passphrase_dict) except: status_msg['status_message'].append( "Passphrase does not represent valid JSON") # Check if already a member of board/list with passphrase chan = Chan.query.filter(Chan.passphrase == passphrase).first() if chan: status_msg['status_message'].append( "You are already a member of this board or list.") if not form_join.pgp_passphrase_msg.data: status_msg['status_message'].append( "Message PGP passphrase required") elif len(form_join.pgp_passphrase_msg.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Message PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) if not form_join.pgp_passphrase_attach.data: status_msg['status_message'].append( "Attachment PGP passphrase required. " "If it's a list you're joining, just leave the default option." ) elif len(form_join.pgp_passphrase_attach.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Attachment PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) if not form_join.pgp_passphrase_steg.data: status_msg['status_message'].append( "Steg PGP passphrase required" "If it's a list you're joining, just leave the default option." ) elif len(form_join.pgp_passphrase_attach.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Steg PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) errors, dict_chan_info = process_passphrase(passphrase) if not dict_chan_info: status_msg['status_message'].append("Error parsing passphrase") for error in errors: status_msg['status_message'].append(error) if not status_msg['status_message']: for each_word in RESTRICTED_WORDS: if each_word in dict_chan_info["label"].lower(): status_msg['status_message'].append( "bitchan is a restricted word for labels") if not status_msg['status_message']: result = daemon_com.join_chan( passphrase, clear_inventory=form_join.resync.data) if dict_chan_info["rules"]: dict_chan_info["rules"] = set_clear_time_to_future( dict_chan_info["rules"]) new_chan = Chan() new_chan.passphrase = passphrase new_chan.access = dict_chan_info["access"] new_chan.type = dict_chan_info["type"] new_chan.label = dict_chan_info["label"] new_chan.description = dict_chan_info["description"] new_chan.primary_addresses = json.dumps( dict_chan_info["primary_addresses"]) new_chan.secondary_addresses = json.dumps( dict_chan_info["secondary_addresses"]) new_chan.tertiary_addresses = json.dumps( dict_chan_info["tertiary_addresses"]) new_chan.restricted_addresses = json.dumps( dict_chan_info["restricted_addresses"]) new_chan.rules = json.dumps(dict_chan_info["rules"]) new_chan.pgp_passphrase_msg = form_join.pgp_passphrase_msg.data new_chan.pgp_passphrase_attach = form_join.pgp_passphrase_attach.data new_chan.pgp_passphrase_steg = form_join.pgp_passphrase_steg.data log_description = None if result.startswith("BM-"): new_chan.address = result new_chan.is_setup = True if new_chan.type == "board": status_msg['status_message'].append("Joined board") url = "/board/{}/1".format(result) url_text = "/{}/ - {}".format(new_chan.label, new_chan.description) log_description = "Joined board {} ({})".format( url_text, result) elif new_chan.type == "list": status_msg['status_message'].append("Joined list") url = "/list/{}".format(result) url_text = "{} - {}".format(new_chan.label, new_chan.description) log_description = "Joined list {} ({})".format( url_text, result) else: status_msg['status_message'].append( "Chan creation queued.") new_chan.address = None new_chan.is_setup = False if log_description: add_mod_log_entry(log_description, message_id=None, user_from=None, board_address=result, thread_hash=None) if 'status_title' not in status_msg: status_msg['status_title'] = "Success" new_chan.save() stage = "end" # Create public/private board/list elif (stage in [ "public_board", "private_board", "public_list", "private_list" ] and form_join.join.data): if not form_join.pgp_passphrase_msg.data: status_msg['status_message'].append( "Message PGP passphrase required") elif len(form_join.pgp_passphrase_msg.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Message PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) if stage in ["public_board", "private_board"]: if not form_join.pgp_passphrase_attach.data: status_msg['status_message'].append( "Attachment PGP passphrase required") elif len(form_join.pgp_passphrase_attach.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Attachment PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) if not form_join.pgp_passphrase_steg.data: status_msg['status_message'].append( "Steg PGP passphrase required") elif len(form_join.pgp_passphrase_steg.data ) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append( "Steg PGP passphrase too long. Max: {}".format( config.PGP_PASSPHRASE_LENGTH)) label = form_join.label.data if not label: status_msg['status_message'].append("Label required") elif len(label) > config.LABEL_LENGTH: status_msg['status_message'].append( "Label too long. Must be {} or fewer characters.".format( config.LABEL_LENGTH)) for each_word in RESTRICTED_WORDS: if each_word in label.lower(): status_msg['status_message'].append( "bitchan is a restricted word for labels") description = form_join.description.data if not description: status_msg['status_message'].append("Description required") elif len(description) > config.DESCRIPTION_LENGTH: status_msg['status_message'].append( "Description too long. Must be {} or fewer characters.". format(config.DESCRIPTION_LENGTH)) def process_additional_addresses(form_list, status_msg): add_list_failed = [] add_list_passed = [] try: list_additional = form_list.split(",") for each_ident in list_additional: ident_strip = each_ident.replace(" ", "") if (ident_strip and (not ident_strip.startswith("BM-") or len(ident_strip) > 38 or len(ident_strip) < 34)): add_list_failed.append(ident_strip) elif ident_strip.startswith("BM-"): add_list_passed.append(ident_strip) except: logger.exception(1) status_msg['status_message'].append( "Error parsing additional addresses. " "Must only be comma-separated addresses without spaces." ) return status_msg, add_list_failed, add_list_passed status_msg, add_list_prim_fail, add_list_prim_pass = process_additional_addresses( form_join.primary_additional.data, status_msg) if add_list_prim_fail: status_msg['status_message'].append( "Error parsing primary additional identities. " "Must only be comma-separated addresses without spaces.") if len(",".join( add_list_prim_pass)) > config.PASSPHRASE_ADDRESSES_LENGTH: status_msg['status_message'].append( "Owner Address list is greater than {} characters: {}". format(config.PASSPHRASE_ADDRESSES_LENGTH, len(",".join(add_list_prim_pass)))) list_primary_identities = [] for key in request.form.keys(): if 'primary_identity_' in key and key[17:]: list_primary_identities.append(key[17:]) list_primary_address_book = [] for key in request.form.keys(): if 'primary_address_book_' in key and key[21:]: list_primary_address_book.append(key[21:]) list_primary_chans = [] for key in request.form.keys(): if 'primary_chans_' in key and key[14:]: list_primary_chans.append(key[14:]) list_primary_addresses = (list_primary_identities + list_primary_address_book + list_primary_chans + add_list_prim_pass) status_msg, add_list_sec_fail, add_list_sec_pass = process_additional_addresses( form_join.secondary_additional.data, status_msg) if add_list_prim_fail: status_msg['status_message'].append( "Error parsing secondary additional identities. " "Must only be comma-separated addresses without spaces.") if len(",".join( add_list_sec_pass)) > config.PASSPHRASE_ADDRESSES_LENGTH: status_msg['status_message'].append( "Admin Address list is greater than {} characters: {}". format(config.PASSPHRASE_ADDRESSES_LENGTH, len(",".join(add_list_sec_pass)))) list_secondary_identities = [] for key in request.form.keys(): if 'secondary_identity_' in key and key[19:]: list_secondary_identities.append(key[19:]) list_secondary_address_book = [] for key in request.form.keys(): if 'secondary_address_book_' in key and key[23:]: list_secondary_address_book.append(key[23:]) list_secondary_chans = [] for key in request.form.keys(): if 'secondary_chans_' in key and key[16:]: list_secondary_chans.append(key[16:]) list_secondary_addresses = (list_secondary_identities + list_secondary_address_book + list_secondary_chans + add_list_sec_pass) status_msg, add_list_ter_fail, add_list_ter_pass = process_additional_addresses( form_join.tertiary_additional.data, status_msg) if add_list_prim_fail: status_msg['status_message'].append( "Error parsing tertiary additional identities. " "Must only be comma-separated addresses without spaces.") if len(",".join( add_list_ter_pass)) > config.PASSPHRASE_ADDRESSES_LENGTH: status_msg['status_message'].append( "User Address list is greater than {} characters: {}". format(config.PASSPHRASE_ADDRESSES_LENGTH, len(",".join(add_list_ter_pass)))) list_tertiary_identities = [] for key in request.form.keys(): if 'tertiary_identity_' in key and key[18:]: list_tertiary_identities.append(key[18:]) list_tertiary_address_book = [] for key in request.form.keys(): if 'tertiary_address_book_' in key and key[22:]: list_tertiary_address_book.append(key[22:]) list_tertiary_chans = [] for key in request.form.keys(): if 'tertiary_chans_' in key and key[15:]: list_tertiary_chans.append(key[15:]) list_tertiary_addresses = (list_tertiary_identities + list_tertiary_address_book + list_tertiary_chans + add_list_ter_pass) status_msg, add_list_restricted_fail, add_list_restricted_pass = process_additional_addresses( form_join.restricted_additional.data, status_msg) if add_list_restricted_fail: status_msg['status_message'].append( "Error parsing restricted additional identities. " "Must only be comma-separated addresses without spaces.") list_restricted_address_book = [] for key in request.form.keys(): if 'restricted_address_book_' in key and key[24:]: list_restricted_address_book.append(key[24:]) list_restricted_chans = [] for key in request.form.keys(): if 'restricted_chans_' in key and key[17:]: list_restricted_chans.append(key[17:]) list_restricted_addresses = (list_restricted_address_book + list_restricted_chans + add_list_restricted_pass) if (stage in ["private_board", "private_list"] and not list_primary_addresses): status_msg['status_message'].append( "Must provide at least one primary address as the owner") rules = {} if form_join.require_identity_to_post.data: rules[ "require_identity_to_post"] = form_join.require_identity_to_post.data if form_join.automatic_wipe.data: if form_join.wipe_epoch.data > config.WIPE_START_MAX: status_msg['status_message'].append( "Automatic Wipe Epoch Start Time is greater than year 3020." ) if form_join.interval_seconds.data > config.WIPE_INTERVAL_MAX: status_msg['status_message'].append( "Automatic Wipe Interval is greater than 500 years.") try: rules["automatic_wipe"] = { "wipe_epoch": form_join.wipe_epoch.data, "interval_seconds": form_join.interval_seconds.data } except: status_msg['status_message'].append( "Could not process Rule options to Automatic Wipe") if form_join.allow_list_pgp_metadata.data: rules[ "allow_list_pgp_metadata"] = form_join.allow_list_pgp_metadata.data extra_string = form_join.extra_string.data if len(extra_string) > config.PASSPHRASE_ADDRESSES_LENGTH: status_msg['status_message'].append( "Extra String is greater than {} characters: {}".format( config.PASSPHRASE_EXTRA_STRING_LENGTH, len(extra_string))) if not status_msg['status_message']: access = stage.split("_")[0] chan_type = stage.split("_")[1] passphrase = generate_passphrase( access, chan_type, label, description, list_restricted_addresses, list_primary_addresses, list_secondary_addresses, list_tertiary_addresses, rules, extra_string) # Check generated passphrase errors, dict_chan_info = process_passphrase(passphrase) if not dict_chan_info: status_msg['status_message'].append( "Error parsing passphrase") for error in errors: status_msg['status_message'].append(error) if not status_msg['status_message']: result = daemon_com.join_chan( passphrase, clear_inventory=form_join.resync.data) if rules: rules = set_clear_time_to_future(rules) new_chan = Chan() new_chan.access = access new_chan.type = chan_type new_chan.label = label new_chan.description = description new_chan.passphrase = passphrase new_chan.restricted_addresses = json.dumps( list_restricted_addresses) new_chan.primary_addresses = json.dumps(list_primary_addresses) new_chan.secondary_addresses = json.dumps( list_secondary_addresses) new_chan.tertiary_addresses = json.dumps( list_tertiary_addresses) new_chan.rules = json.dumps(rules) new_chan.pgp_passphrase_msg = form_join.pgp_passphrase_msg.data new_chan.pgp_passphrase_attach = form_join.pgp_passphrase_attach.data new_chan.pgp_passphrase_steg = form_join.pgp_passphrase_steg.data log_description = None if result.startswith("BM-"): if stage == "public_board": status_msg['status_message'].append( "Created public board") elif stage == "private_board": status_msg['status_message'].append( "Created private board") elif stage == "public_list": status_msg['status_message'].append( "Created public list") elif stage == "private_list": status_msg['status_message'].append( "Created private list") new_chan.address = result new_chan.is_setup = True if stage in ["public_board", "private_board"]: url = "/board/{}/1".format(result) url_text = "/{}/ - {}".format(label, description) log_description = "Created board {} ({})".format( url_text, result) elif stage in ["public_list", "private_list"]: url = "/list/{}".format(result) url_text = "{} - {}".format(label, description) log_description = "Created list {} ({})".format( url_text, result) else: status_msg['status_message'].append("Creation queued") new_chan.address = None new_chan.is_setup = False if log_description: add_mod_log_entry(log_description, message_id=None, user_from=None, board_address=result, thread_hash=None) if 'status_title' not in status_msg: status_msg['status_title'] = "Success" new_chan.save() stage = "end" if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return render_template("pages/join.html", stage=stage, status_msg=status_msg, url=url, url_text=url_text)
def identities(): global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg form_identity = forms_settings.Identity() form_confirm = forms_board.Confirm() status_msg = session.get('status_msg', {"status_message": []}) if request.method == 'GET': if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': if form_identity.create_identity.data: if not form_identity.label.data or not form_identity.passphrase.data: status_msg['status_message'].append("Label and passphrase required") errors, dict_chan_info = process_passphrase(form_identity.passphrase.data) if dict_chan_info: status_msg['status_message'].append("Cannot create an Identity with board/list passphrase") if not status_msg['status_message']: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: b64_passphrase = base64.b64encode(form_identity.passphrase.data.encode()) return_str = api.createDeterministicAddresses(b64_passphrase.decode()) if return_str: if ("addresses" in return_str and len(return_str["addresses"]) == 1 and return_str["addresses"][0]): ident = Identity.query.filter( Identity.address == return_str["addresses"][0]).first() if ident: logger.info( "Creating identity that already exists in the database. " "Skipping adding entry") else: new_ident = Identity() new_ident.address = return_str["addresses"][0] new_ident.label = form_identity.label.data new_ident.passphrase_base64 = b64_passphrase new_ident.save() daemon_com.refresh_identities() if form_identity.resync.data: daemon_com.signal_clear_inventory() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Created identity {} with address {}.".format( form_identity.label.data, return_str["addresses"][0])) status_msg['status_message'].append( "Give the system a few seconds for the change to take effect.") else: status_msg['status_message'].append( "Error creating Identity: {}".format(return_str)) else: status_msg['status_message'].append("Error creating Identity") finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) elif form_identity.rename.data: if not form_identity.ident_label.data or not form_identity.address.data: status_msg['status_message'].append("Label and address required") if not status_msg['status_message']: ident = Identity.query.filter( Identity.address == form_identity.address.data).first() if ident: ident.label = form_identity.ident_label.data ident.save() daemon_com.refresh_identities() status_msg['status_title'] = "Success" status_msg['status_message'].append("Identity renamed.") status_msg['status_message'].append( "Give the system a few seconds for the change to take effect.") elif form_identity.delete.data: ident = None if not form_identity.address.data: status_msg['status_message'].append("Address required") else: ident = Identity.query.filter( Identity.address == form_identity.address.data).first() if not form_confirm.confirm.data: return render_template("pages/confirm.html", action="delete_identity", ident=ident) if not status_msg['status_message']: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: return_str = api.deleteAddress(form_identity.address.data) if return_str == "success": if ident: ident.delete() daemon_com.refresh_identities() status_msg['status_title'] = "Success" status_msg['status_message'].append("Identity deleted.") status_msg['status_message'].append( "Give the system a few seconds for the change to take effect.") else: status_msg['status_message'].append( "Error deleting Identity: {}".format(return_str)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) session['status_msg'] = status_msg if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return redirect(url_for("routes_identities.identities")) return render_template("pages/identities.html", form_identity=form_identity, status_msg=status_msg)
def format_body(message_id, body, truncate): """ Formatting of post body text at time of page render Mostly to allow links to properly form after initial message processing from bitmessage """ if not body: return "" split = False this_message = Messages.query.filter( Messages.message_id == message_id).first() lines = body.split("<br/>") if ((truncate and len(lines) > config.BOARD_MAX_LINES) or (truncate and len(body) > config.BOARD_MAX_CHARACTERS)): split = True if split: lines = lines[:config.BOARD_MAX_LINES] total_popups = 0 regex_passphrase = r"""(\[\"(private|public)\"\,\s\"(board|list)\"\,\s\".{1,25}?\"\,\s\".{1,128}?\"\,\s\[(\"BM\-[a-zA-Z0-9]{32,34}(\"\,\s)?|\"?)*\]\,\s\[(\"BM\-[a-zA-Z0-9]{32,34}(\"\,\s)?|\"?)*\]\,\s\[(\"BM\-[a-zA-Z0-9]{32,34}(\"\,\s)?|\"?)*\]\,\s\[((\"BM\-[a-zA-Z0-9]{32,34}(\"\,\s)?|\"?)*\]\,\s\{(.*?)(\})|(\}\}))\,\s\"(.*?)\"\])""" regex_passphrase_base64_link = r"""http\:\/\/(172\.28\.1\.1|\blocalhost\b)\:8000\/join_base64\/([A-Za-z0-9+&]+={0,2})(\s|\Z)""" regex_address = r'(\[identity\](BM\-[a-zA-Z0-9]{32,34})\[\/identity\])' # Used to store multi-line strings to replace >>> crosspost text. # Needs to occur at end after all crossposts have been found. dict_replacements = {} for line in range(len(lines)): # Search and append identity addresses with useful links number_finds = len( re.findall(regex_address, html.unescape(lines[line]))) for i in range(number_finds): each_find = re.search(regex_address, html.unescape(lines[line])) to_replace = each_find.groups()[0] address = each_find.groups()[1] identity = Identity.query.filter( Identity.address == address).first() address_book = AddressBook.query.filter( AddressBook.address == address).first() replaced_code = """<img style="width: 15px; height: 15px; position: relative; top: 3px;" src="/icon/{0}"> <span class="replace-funcs">{0}</span> (<button type="button" class="btn" title="Copy to Clipboard" onclick="CopyToClipboard('{0}')">📋</button>""".format( address) if identity: replaced_code += ' <span class="replace-funcs">You, {}</span>'.format( identity.label) elif address_book: replaced_code += ' <span class="replace-funcs">{},</span>' \ ' <a class="link" href="/compose/0/{}">Send Message</a>'.format( address_book.label, address) else: replaced_code += ' <a class="link" href="/compose/0/{0}">Send Message</a>,' \ ' <a class="link" href="/address_book_add/{0}">Add to Address Book</a>'.format( address) replaced_code += ')' lines[line] = lines[line].replace(to_replace, replaced_code, 1) # Search and replace Board/List Passphrase with link number_finds = len( re.findall(regex_passphrase, html.unescape(lines[line]))) for i in range(number_finds): url = None each_find = re.search(regex_passphrase, html.unescape(lines[line])) passphrase = each_find.groups()[0] passphrase_escaped = html.escape(passphrase) # Find if passphrase already exists (already joined board/list) chan = Chan.query.filter(Chan.passphrase == passphrase).first() if chan: link_text = "" if chan.access == "public": link_text += "Public " elif chan.access == "private": link_text += "Private " if chan.type == "board": link_text += "Board " elif chan.type == "list": link_text += "List " link_text += "/{}/".format(chan.label) if chan.type == "board": url = '<a class="link" href="/board/{a}/1" title="{d}">{l}</a>'.format( a=chan.address, d=chan.description, l=link_text) elif chan.type == "list": url = '<a class="link" href="/list/{a}" title="{d}">{l}</a>'.format( a=chan.address, d=chan.description, l=link_text) else: errors, pass_info = process_passphrase(passphrase) if errors: continue link_text = "" if pass_info["access"] == "public": link_text += "Public " elif pass_info["access"] == "private": link_text += "Private " if pass_info["type"] == "board": link_text += "Board " elif pass_info["type"] == "list": link_text += "List " link_text += "/{}/".format(pass_info["label"]) url = """<a class="link" href="/join_base64/{p}" title="{d}">{l} (Click to Join)</a>""".format( p=base64.b64encode(passphrase.encode()).decode(), d=pass_info["description"], l=link_text) if url: lines[line] = lines[line].replace(passphrase_escaped, url, 1) # Search and replace Board/List Passphrase base64 links with friendlier link number_finds = len( re.findall(regex_passphrase_base64_link, html.unescape(lines[line]))) for i in range(number_finds): url = None each_find = re.search(regex_passphrase_base64_link, html.unescape(lines[line])) link = each_find.group() if len(each_find.groups()) < 2: continue passphrase_encoded = each_find.groups()[1] passphrase_dict_json = base64.b64decode( passphrase_encoded.replace("&", "/")).decode() passphrase_dict = json.loads(passphrase_dict_json) passphrase_decoded = passphrase_dict["passphrase"] # Find if passphrase already exists (already joined board/list) chan = Chan.query.filter( Chan.passphrase == passphrase_decoded).first() if chan: link_text = "" if chan.access == "public": link_text += "Public " elif chan.access == "private": link_text += "Private " if chan.type == "board": link_text += "Board " elif chan.type == "list": link_text += "List " link_text += "/{}/".format(chan.label) if chan.type == "board": url = '<a class="link" href="/board/{a}/1" title="{d}">{l}</a>'.format( a=chan.address, d=chan.description, l=link_text) elif chan.type == "list": url = '<a class="link" href="/list/{a}" title="{d}">{l}</a>'.format( a=chan.address, d=chan.description, l=link_text) else: errors, pass_info = process_passphrase(passphrase_decoded) if errors: logger.error( "Errors parsing passphrase: {}".format(errors)) continue link_text = "" if pass_info["access"] == "public": link_text += "Public " elif pass_info["access"] == "private": link_text += "Private " if pass_info["type"] == "board": link_text += "Board " elif pass_info["type"] == "list": link_text += "List " link_text += "/{}/".format(pass_info["label"]) url = """<a class="link" href="/join_base64/{p}" title="{d}">{l} (Click to Join)</a>""".format( p=passphrase_encoded, d=pass_info["description"], l=link_text) if url: lines[line] = lines[line].replace(link.strip(), url, 1) # Search and replace BM address with post ID with link dict_chans_threads_strings = is_board_post_reply(lines[line]) if dict_chans_threads_strings: for each_string, each_address in dict_chans_threads_strings.items( ): total_popups += lines[line].count(each_string) if total_popups > 50: break board_address = each_address.split("/")[0] board_post_id = each_address.split("/")[1] chan_entry = db_return(Chan).filter( and_(Chan.type == "board", Chan.address == board_address)).first() if chan_entry: message = db_return(Messages).filter( Messages.post_id == board_post_id).first() if message: link_text = '>>>/{l}/{p}'.format( l=html.escape(chan_entry.label), p=message.post_id) rep_str = get_reply_link_html(message, external_thread=True, external_board=True, link_text=link_text) # Store replacement in dict to conduct after all matches have been found new_id = str(uuid.uuid4()) dict_replacements[new_id] = rep_str lines[line] = lines[line].replace(each_string, new_id) # Search and replace only BM address with link dict_chans_strings = is_chan_reply(lines[line]) if dict_chans_strings: for each_string, each_address in dict_chans_strings.items(): chan_entry = db_return(Chan).filter( and_(Chan.type == "board", Chan.address == each_address)).first() list_entry = db_return(Chan).filter( and_(Chan.type == "list", Chan.address == each_address)).first() if chan_entry: lines[line] = lines[line].replace( each_string, '<a class="link" href="/board/{a}/1" title="{d}">>>>/{l}/</a>' .format(a=each_address, d=chan_entry.description.replace( '"', '"'), l=html.escape(chan_entry.label))) elif list_entry: lines[line] = lines[line].replace( each_string, '<a class="link" href="/list/{a}" title="{d}">>>>/{l}/</a>' .format(a=each_address, d=list_entry.description, l=list_entry.label)) # Find and replace hyperlinks list_links = [] for each_word in lines[line].split(" "): parsed = parse.urlparse(each_word) if parsed.scheme and parsed.netloc: if ">" not in parsed.geturl(): list_links.append(parsed.geturl()) for each_link in list_links: lines[line] = lines[line].replace( each_link, '<a class="link" href="{l}" target="_blank">{l}</a>'.format( l=each_link)) # Search and replace Post Reply ID with link # Must come after replacement of hyperlinks dict_ids_strings = is_post_id_reply(lines[line]) if dict_ids_strings: for each_string, targetpostdata in dict_ids_strings.items(): total_popups += lines[line].count(each_string) if total_popups > 50: break # Determine if OP or identity/address book label is to be appended to reply post ID message = Messages.query.filter( Messages.post_id == targetpostdata["id"]).first() name_str = "" self_post = False if message: identity = Identity.query.filter( Identity.address == message.address_from).first() if not name_str and identity and identity.label: self_post = True name_str = " ({})".format(identity.label) address_book = AddressBook.query.filter( AddressBook.address == message.address_from).first() if not name_str and address_book and address_book.label: name_str = " ({})".format(address_book.label) # Same-thread reference if (targetpostdata["location"] == "local" and message and message.thread and this_message and this_message.thread and message.thread.thread_hash == this_message.thread.thread_hash): if message.thread.op_sha256_hash == message.message_sha256_hash: name_str = " (OP)" rep_str = get_reply_link_html(message, self_post=self_post, name_str=name_str) # Off-board cross-post elif (targetpostdata["location"] == "remote" and message and message.thread and this_message and this_message.thread and message.thread.thread_hash != this_message.thread.thread_hash and message.thread.chan.address != this_message.thread.chan.address): rep_str = get_reply_link_html(message, self_post=self_post, name_str=name_str, external_thread=True, external_board=True) # Off-thread cross-post elif (targetpostdata["location"] == "remote" and message and message.thread and this_message and this_message.thread and message.thread.thread_hash != this_message.thread.thread_hash): rep_str = get_reply_link_html(message, self_post=self_post, name_str=name_str, external_thread=True) # No reference/cross-post found else: rep_str = each_string # Store replacement in dict to conduct after all matches have been found new_id = str(uuid.uuid4()) dict_replacements[new_id] = rep_str lines[line] = lines[line].replace(each_string, new_id) return_body = "<br/>".join(lines) for id_to_replace, replace_with in dict_replacements.items(): return_body = return_body.replace(id_to_replace, replace_with) if split: truncate_str = '<br/><br/><span class="expand">Comment truncated. ' \ '<a class="link" href="/thread/{ca}/{th}#{pid}">Click here</a>' \ ' to view the full post.</span>'.format( ca=this_message.thread.chan.address, th=this_message.thread.thread_hash_short, pid=this_message.post_id) return_body += truncate_str return return_body
def process_admin(msg_dict, msg_decrypted_dict): """Process message as an admin command""" logger.info("{}: Message is an admin command".format( msg_dict["msgid"][-config.ID_LENGTH:].upper())) # Authenticate sender with session_scope(DB_PATH) as new_session: chan = new_session.query(Chan).filter( Chan.address == msg_dict['toAddress']).first() if chan: errors, dict_info = process_passphrase(chan.passphrase) # Message must be from address in primary or secondary access list access = get_access(msg_dict['toAddress']) if errors or (msg_dict['fromAddress'] not in access["primary_addresses"] and msg_dict['fromAddress'] not in access["secondary_addresses"]): logger.error( "{}: Unauthorized Admin message. Deleting.".format( msg_dict["msgid"][-config.ID_LENGTH:].upper())) daemon_com.trash_message(msg_dict["msgid"]) return else: logger.error("{}: Admin message: Chan not found".format( msg_dict["msgid"][-config.ID_LENGTH:].upper())) daemon_com.trash_message(msg_dict["msgid"]) return logger.info( "{}: Admin message received from {} for {} is authentic".format( msg_dict["msgid"][-config.ID_LENGTH:].upper(), msg_dict['fromAddress'], msg_dict['toAddress'])) admin_dict = { "timestamp_utc": 0, "chan_type": None, "action": None, "action_type": None, "options": {}, "thread_id": None, "message_id": None, "chan_address": None } if "timestamp_utc" in msg_decrypted_dict and msg_decrypted_dict[ "timestamp_utc"]: admin_dict["timestamp_utc"] = msg_decrypted_dict["timestamp_utc"] if "chan_type" in msg_decrypted_dict and msg_decrypted_dict["chan_type"]: admin_dict["chan_type"] = msg_decrypted_dict["chan_type"] if "action" in msg_decrypted_dict and msg_decrypted_dict["action"]: admin_dict["action"] = msg_decrypted_dict["action"] if "action_type" in msg_decrypted_dict and msg_decrypted_dict[ "action_type"]: admin_dict["action_type"] = msg_decrypted_dict["action_type"] if "options" in msg_decrypted_dict and msg_decrypted_dict["options"]: admin_dict["options"] = msg_decrypted_dict["options"] if "thread_id" in msg_decrypted_dict and msg_decrypted_dict["thread_id"]: admin_dict["thread_id"] = msg_decrypted_dict["thread_id"] if "message_id" in msg_decrypted_dict and msg_decrypted_dict["message_id"]: admin_dict["message_id"] = msg_decrypted_dict["message_id"] if "chan_address" in msg_decrypted_dict and msg_decrypted_dict[ "chan_address"]: admin_dict["chan_address"] = msg_decrypted_dict["chan_address"] access = get_access(msg_dict['toAddress']) lf = LF() if lf.lock_acquire(config.LOCKFILE_ADMIN_CMD, to=20): try: # (Owner): set board options if (admin_dict["action"] == "set" and admin_dict["action_type"] == "options" and msg_dict['fromAddress'] in access["primary_addresses"]): admin_set_options(msg_dict, admin_dict) # (Owner, Admin): set thread options elif ( admin_dict["action"] == "set" and admin_dict["action_type"] == "thread_options" and (msg_dict['fromAddress'] in access["primary_addresses"] or msg_dict['fromAddress'] in access["secondary_addresses"])): admin_set_thread_options(msg_dict, admin_dict) # (Owner, Admin): delete board thread or post elif ( admin_dict["action"] == "delete" and admin_dict["chan_type"] == "board" and (msg_dict['fromAddress'] in access["primary_addresses"] or msg_dict['fromAddress'] in access["secondary_addresses"])): admin_delete_from_board(msg_dict, admin_dict) # (Owner, Admin): delete board post with comment elif ( admin_dict["action"] == "delete_comment" and admin_dict["action_type"] == "post" and "options" in admin_dict and "delete_comment" in admin_dict["options"] and "message_id" in admin_dict["options"]["delete_comment"] and "comment" in admin_dict["options"]["delete_comment"] and (msg_dict['fromAddress'] in access["primary_addresses"] or msg_dict['fromAddress'] in access["secondary_addresses"])): admin_delete_from_board_with_comment(msg_dict, admin_dict) # (Owner, Admin): Ban user elif ( admin_dict["action"] in ["board_ban_silent", "board_ban_public"] and admin_dict["action_type"] in "ban_address" and admin_dict["options"] and "ban_address" in admin_dict["action_type"] and (msg_dict['fromAddress'] in access["primary_addresses"] or msg_dict['fromAddress'] in access["secondary_addresses"])): admin_ban_address_from_board(msg_dict, admin_dict) else: logger.error("{}: Unknown Admin command. Deleting. {}".format( msg_dict["msgid"][-config.ID_LENGTH:].upper(), admin_dict)) daemon_com.trash_message(msg_dict["msgid"]) except Exception: logger.exception( "{}: Exception processing Admin command. Deleting.".format( msg_dict["msgid"][-config.ID_LENGTH:].upper())) daemon_com.trash_message(msg_dict["msgid"]) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API)