def images(message_id, file_type, filename): can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg filename = unquote(filename) post = Messages.query.filter(Messages.message_id == message_id).first() if not post: if file_type == "god_song": pass # God songs can be in the long description else: return "Message ID not found" if file_type == "thumb": # Return image thumbnail file_order, media_info, _ = attachment_info(message_id) path = "{}/{}_thumb".format(config.FILE_DIRECTORY, message_id) if filename in media_info: return get_image_attach( message_id, path, filename, media_info[filename]["extension"]) if file_type == "thumb_first": # Return image thumbnail file_order, media_info, _ = attachment_info(message_id) path = "{}/{}_thumb".format(config.FILE_DIRECTORY, message_id) for filename in media_info: if media_info[filename]["extension"] in config.FILE_EXTENSIONS_IMAGE: if media_info[filename]['spoiler']: return send_file("/home/bitchan/static/spoiler.png", mimetype="image/png") else: return get_image_attach( message_id, path, filename, media_info[filename]["extension"]) elif file_type == "image": # Return image file file_order, media_info, _ = attachment_info(message_id) path = "{}/{}".format(config.FILE_DIRECTORY, message_id) if filename in media_info: return get_image_attach( message_id, path, filename, media_info[filename]["extension"]) elif file_type == "god_song": file_path = "{}/{}_god_song.mp3".format(config.FILE_DIRECTORY, message_id) if os.path.exists(file_path): return send_file(file_path, mimetype="audio/mp3") else: return "Could not find God Song file at {}".format(file_path) elif file_type == "file": # Return potentially non-image file file_order, media_info, _ = attachment_info(message_id) path = "{}/{}".format(config.FILE_DIRECTORY, message_id) file_path = "{}/{}".format(path, filename) if os.path.exists(file_path): if (filename in media_info and media_info[filename]["extension"] in config.FILE_EXTENSIONS_IMAGE): return get_image_attach( message_id, path, filename, media_info[filename]["extension"]) else: return send_file(file_path, attachment_filename=filename) else: logger.error("File '{}' not found for message {}".format(filename, message_id)) return ""
def spoiler_image(chan_address): """Returns a spoiler image based on whether a custom banner is available""" can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg chan = Chan.query.filter(Chan.address == chan_address).first() if chan: admin_cmd = Command.query.filter(and_( Command.chan_address == chan.address, Command.action == "set", Command.action_type == "options")).first() if admin_cmd: try: options = json.loads(admin_cmd.options) except: options = {} if "spoiler_base64" in options and options["spoiler_base64"]: return send_file( BytesIO(base64.b64decode(options["spoiler_base64"])), mimetype='image/png', cache_timeout=1440) file_path = "/home/bitchan/static/spoiler.png" return send_file(file_path, mimetype='image/png', cache_timeout=1440)
def icon_image(address): can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg path_icon = os.path.join(config.FILE_DIRECTORY, "{}_icon.png".format(address)) if not os.path.exists(path_icon): generate_icon(address) return send_file(path_icon, mimetype='image/png', cache_timeout=1440)
def download(message_id, filename): can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg post = Messages.query.filter(Messages.message_id == message_id).first() file_path = "{}/{}/{}".format(config.FILE_DIRECTORY, message_id, filename) if post and os.path.exists(file_path): return send_file(file_path, attachment_filename=filename, as_attachment=True)
def verify_wait(): allowed, allow_msg = allowed_access(check_can_view=True) if not allowed: return allow_msg if "session_id" not in session: session["session_id"] = str(uuid.uuid4()) page_id = str(uuid.uuid4()) session[page_id] = time.time() return render_template("pages/verify_wait.html", page_id=page_id)
def custom_flag_by_post_id(post_id): """Returns a flag image based on the post ID""" can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg message = Messages.query.filter(Messages.message_id == post_id).first() if message: return send_file( BytesIO(base64.b64decode(message.nation_base64)), mimetype='image/jpg', cache_timeout=1440)
def custom_flag_by_flag_id(flag_id): """Returns a flag image based on the flag ID""" can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg flag = Flags.query.filter(Flags.id == int(flag_id)).first() if flag: return send_file( BytesIO(base64.b64decode(flag.flag_base64)), mimetype='image/{}'.format(flag.flag_extension), cache_timeout=1440)
def verify_test(page_id): allowed, allow_msg = allowed_access(check_can_view=True) if not allowed: return allow_msg if page_id not in session or "session_id" not in session: return '<div style="text-align:center;padding-top:2em">Invalid ID. <a href="/">Reverify</a></div>' ts = session[page_id] if time.time() < ts + 5: session.pop(page_id) return '<div style="text-align:center;padding-top:2em">Invalid Wait. <a href="/">Reverify</a></div>' if request.method == 'POST': page_id = request.form.get('page_id', None) if captcha.validate(page_id=page_id): session_test = SessionInfo.query.filter( SessionInfo.session_id == session["session_id"]).first() if not session_test: session_info = SessionInfo() session_info.session_id = session["session_id"] session_info.request_rate_ts = time.time() session_info.request_rate_amt = 0 session_info.verified = True session_info.save() else: session_test.session_id = session["session_id"] session_test.request_rate_ts = time.time() session_test.request_rate_amt = 0 session_test.verified = True session_test.save() session["verified"] = True session.pop(page_id) logger.info("Post request session: {}".format(session)) return redirect(url_for('routes_main.index')) else: if "verify_captcha_count" not in session: session["verify_captcha_count"] = 1 elif session["verify_captcha_count"] > 4: session["verify_captcha_count"] = 0 session.pop(page_id) return redirect(url_for('routes_verify.verify_wait')) else: session["verify_captcha_count"] += 1 return render_template("pages/verify_test.html", page_id=page_id)
def banner_image(chan_address): """Returns a banner image based on whether a custom banner is available""" file_path = None settings = GlobalSettings.query.first() can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: if settings.theme in config.THEMES_DARK: file_path = "/home/bitchan/static/banner_dark.png" elif settings.theme in config.THEMES_LIGHT: file_path = "/home/bitchan/static/banner_light.png" if file_path: return send_file(file_path, mimetype='image/png', cache_timeout=1440) else: return allow_msg chan = Chan.query.filter(Chan.address == chan_address).first() if chan: admin_cmd = Command.query.filter(and_( Command.chan_address == chan.address, Command.action == "set", Command.action_type == "options")).first() if admin_cmd: try: options = json.loads(admin_cmd.options) except: options = {} if "banner_base64" in options and options["banner_base64"]: return send_file( BytesIO(base64.b64decode(options["banner_base64"])), mimetype='image/png', cache_timeout=1440) if settings.theme in config.THEMES_DARK: file_path = "/home/bitchan/static/banner_dark.png" elif settings.theme in config.THEMES_LIGHT: file_path = "/home/bitchan/static/banner_light.png" if file_path: return send_file(file_path, mimetype='image/png', cache_timeout=1440) return "Error determining the banner image to use"
def login_info(): allowed, allow_msg = allowed_access() if not allowed: return allow_msg status_msg = {"status_message": []} board = {"current_chan": None} if ('uuid' in session and session['uuid'] in flask_session_login and 'logged_in' in flask_session_login[session['uuid']] and flask_session_login[session['uuid']]['logged_in'] and 'credentials' in flask_session_login[session['uuid']]): login_credentials = flask_session_login[session['uuid']]['credentials'] login_credentials['uuid'] = session['uuid'] else: login_credentials = None return render_template("pages/login_info.html", board=board, login_credentials=login_credentials, status_msg=status_msg)
def leave(address): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg form_confirm = forms_board.Confirm() chan = Chan.query.filter(Chan.address == address).first() if request.method != 'POST' or not form_confirm.confirm.data: return render_template("pages/confirm.html", action="leave", address=address, chan=chan) status_msg = {"status_message": []} admin_cmds = Command.query.filter(Command.chan_address == address).all() for each_adm_cmd in admin_cmds: each_adm_cmd.delete() lf = LF() if lf.lock_acquire(config.LOCKFILE_MSG_PROC, to=60): try: for each_thread in chan.threads: for each_message in each_thread.messages: delete_post(each_message.message_id) # Delete thread posts delete_thread(each_thread.thread_hash) # Delete thread deleted_msgs = DeletedMessages.query.filter( DeletedMessages.address_to == address).all() for each_msg in deleted_msgs: logger.info("DeletedMessages: Deleting entry: {}".format( each_msg.message_id)) each_msg.delete() try: daemon_com.leave_chan(address) # Leave chan in Bitmessage delete_chan(address) # Delete chan # Delete mod log entries for address mod_logs = ModLog.query.filter( ModLog.board_address == address).all() for each_entry in mod_logs: each_entry.delete() except: logger.exception( "Could not delete chan via daemon or delete_chan()") daemon_com.delete_and_vacuum() status_msg['status_title'] = "Success" status_msg['status_message'].append("Deleted {}".format(address)) finally: time.sleep(1) lf.lock_release(config.LOCKFILE_MSG_PROC) board = {"current_chan": None} url = "" url_text = "" return render_template("pages/alert.html", board=board, 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 diag(): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg status_msg = session.get('status_msg', {"status_message": []}) form_diag = forms_settings.Diag() # get all messages sending import sqlite3 from binascii import hexlify row = [] try: conn = sqlite3.connect('file:{}'.format(config.messages_dat), uri=True) conn.text_factory = bytes c = conn.cursor() c.execute( "SELECT msgid, fromaddress, toaddress, lastactiontime, message, status " "FROM sent " "WHERE folder='sent'") row = c.fetchall() conn.commit() conn.close() except Exception as err: logger.exception("Error checking for POW: {}".format(err)) # Convert msg IDs sending_msgs = [] for each_row in row: if each_row[5].decode() in ["doingmsgpow", "msgqueued"]: sending_msgs.append( (hexlify(each_row[0]).decode(), each_row[1].decode(), each_row[2].decode(), each_row[3], len(each_row[4]), each_row[5].decode())) if request.method == 'POST': if form_diag.del_sending_msg.data: cancel_send_id_list = [] for each_input in request.form: if each_input.startswith("delsendingmsgid_"): cancel_send_id_list.append(each_input.split("_")[1]) if not cancel_send_id_list: status_msg['status_message'].append( "Must select at least one message to cancel the sending of." ) if not status_msg['status_message']: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: for each_id in cancel_send_id_list: logger.info( "Trashing msg with ID: {}".format(each_id)) api.trashSentMessage(each_id) time.sleep(0.1) time.sleep(1) daemon_com.restart_bitmessage() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Deleted message(s) being sent and restarting Bitmessage. " "Please wait at least 60 seconds before canceling another send." ) except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) if form_diag.del_inventory.data: try: daemon_com.clear_bm_inventory() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Deleted Bitmessage inventory and restarting Bitmessage. Give it time to resync." ) except Exception as err: status_msg['status_message'].append( "Couldn't delete Bitmessage inventory: {}".format(err)) logger.exception("Couldn't delete BM inventory") elif form_diag.del_deleted_msg_db.data: try: deleted_msgs = DeletedMessages.query.all() for each_msg in deleted_msgs: logger.info("DeletedMessages: Deleting entry: {}".format( each_msg.message_id)) each_msg.delete() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Cleared Deleted Message table") except Exception as err: status_msg['status_message'].append( "Couldn't clear Deleted Message table: {}".format(err)) logger.exception("Couldn't clear Deleted Message table") elif form_diag.del_non_bc_msg_list.data: try: settings = GlobalSettings.query.first() settings.discard_message_ids = "[]" settings.save() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Cleared Non-BC Message List") except Exception as err: status_msg['status_message'].append( "Couldn't clear Non-BC Message List: {}".format(err)) logger.exception("Couldn't clear Non-BC Message List") elif form_diag.del_trash.data: try: daemon_com.delete_and_vacuum() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Deleted Bitmessage Trash items.") except Exception as err: status_msg['status_message'].append( "Couldn't delete Bitmessage Trash items: {}".format(err)) logger.exception("Couldn't delete BM Trash Items") elif form_diag.del_popup_html.data: try: for each_message in Messages.query.all(): each_message.popup_html = "" each_message.save() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Deleted popup HTML for all messages.") except Exception as err: status_msg['status_message'].append( "Couldn't delete popup HTML: {}".format(err)) logger.exception("Couldn't delete popup HTML") elif form_diag.del_cards.data: try: cards = PostCards.query.all() for each_card in cards: each_card.delete() status_msg['status_title'] = "Success" status_msg['status_message'].append("Deleted cards.") except Exception as err: status_msg['status_message'].append( "Couldn't delete cards: {}".format(err)) logger.exception("Couldn't delete cards") elif form_diag.del_mod_log.data: try: mod_logs = ModLog.query.all() for each_entry in mod_logs: each_entry.delete() status_msg['status_title'] = "Success" status_msg['status_message'].append("Deleted Mod Log.") except Exception as err: status_msg['status_message'].append( "Couldn't delete Mod Log: {}".format(err)) logger.exception("Couldn't delete Mod Log") elif form_diag.del_posts_without_thread.data: try: messages = Messages.query.all() for each_msg in messages: if not each_msg.thread: each_msg.delete() status_msg['status_title'] = "Success" status_msg['status_message'].append("Deleted orphaned posts.") except Exception as err: status_msg['status_message'].append( "Couldn't delete orphaned posts: {}".format(err)) logger.exception("Couldn't delete orphaned posts") elif form_diag.fix_thread_board_timestamps.data: try: threads = Threads.query.all() for each_thread in threads: latest_post = Messages.query.filter( Messages.thread_id == each_thread.id).order_by( Messages.timestamp_sent.desc()).first() if latest_post: each_thread.timestamp_sent = latest_post.timestamp_sent each_thread.save() boards = Chan.query.filter(Chan.type == "board").all() for each_board in boards: latest_thread = Threads.query.filter( Threads.chan_id == each_board.id).order_by( Threads.timestamp_sent.desc()).first() if latest_thread: each_board.timestamp_sent = latest_thread.timestamp_sent each_board.save() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Fixed thread and board timestamps.") except Exception as err: status_msg['status_message'].append( "Couldn't fix thread and board timestamps: {}".format(err)) logger.exception("Couldn't fix thread and board timestamps") elif form_diag.fix_thread_short_hashes.data: try: threads = Threads.query.all() for each_thread in threads: each_thread.thread_hash_short = each_thread.thread_hash[ -12:] each_thread.save() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Fixed thread short hashes") except Exception as err: status_msg['status_message'].append( "Couldn't fix thread short hashes: {}".format(err)) logger.exception("Couldn't fix thread short hashes") elif form_diag.download_backup.data: date_now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') filename = 'bitchan-backup_{}.tar'.format(date_now) save_path = '/home/{}'.format(filename) def delete_backup_files(): time.sleep(7200) delete_files = glob.glob("/home/*.tar") delete_files.append( '/home/bitchan/bitchan_backup-usr_bitchan.tar') delete_files.append( '/home/bitchan/bitchan_backup-usr_bitmessage.tar') for each_file in delete_files: delete_file(each_file) try: cmd = 'tar -cvf /home/bitchan/bitchan_backup-usr_bitchan.tar /usr/local/bitchan' output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) cmd = 'tar -cvf /home/bitchan/bitchan_backup-usr_bitmessage.tar /usr/local/bitmessage' output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) cmd = 'tar -cvf {} /home/bitchan'.format(save_path) output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) thread_download = Thread(target=delete_backup_files) thread_download.start() return send_file(save_path, mimetype='application/x-tar') except Exception as err: status_msg['status_message'].append( "Couldn't generate backup archive: {}".format(err)) logger.exception("Couldn't generate backup archive") elif form_diag.download_backup.data: try: save_path = '/tmp/bitchan-backup_to_restore.tar' delete_file(save_path) form_diag.restore_backup_file.data.save(save_path) cmd = 'tar -xvf {} -C /'.format(save_path) output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) cmd = 'tar -xvf /home/bitchan/bitchan_backup-usr_bitchan.tar -C /' output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) cmd = 'tar -xvf /home/bitchan/bitchan_backup-usr_bitmessage.tar -C /' output = subprocess.check_output(cmd, shell=True, text=True) logger.debug("Command: {}, Output: {}".format(cmd, output)) def delete_backup_files(): delete_files = [ save_path, '/home/bitchan/bitchan_backup-usr_bitchan.tar', '/home/bitchan/bitchan_backup-usr_bitmessage.tar' ] for each_file in delete_files: delete_file(each_file) subprocess.Popen('docker stop -t 15 bitchan_daemon 2>&1', shell=True) time.sleep(15) subprocess.Popen('docker start bitchan_daemon 2>&1', shell=True) subprocess.Popen('docker stop -t 15 bitmessage 2>&1', shell=True) time.sleep(15) subprocess.Popen('docker start bitmessage 2>&1', shell=True) thread_download = Thread(target=delete_backup_files) thread_download.start() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Restored backup and restarted Bitmessage and BitChan") except Exception as err: status_msg['status_message'].append( "Couldn't restore backup: {}".format(err)) logger.exception("Couldn't restore backup archive") elif form_diag.bulk_delete_threads_submit.data: address = "0" if form_diag.bulk_delete_threads_address.data: board = Chan.query.filter( Chan.address == form_diag.bulk_delete_threads_address.data) if not board.count(): status_msg['status_message'].append( "Invalid Address: {}".format( form_diag.bulk_delete_threads_address.data)) else: address = board.address return redirect( url_for("routes_admin.bulk_delete_thread", current_chan=address)) if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return render_template("pages/diag.html", flask_session_login=flask_session_login, form_diag=form_diag, replace_lt_gt=replace_lt_gt, sending_msgs=sending_msgs, settings=GlobalSettings.query.first(), status_msg=status_msg, themes=themes.themes)
def bug_report(): allowed, allow_msg = allowed_access(check_can_view=True) if not allowed: return allow_msg status_msg = session.get('status_msg', {"status_message": []}) form_bug = forms_board.BugReport() if request.method == 'POST': if form_bug.send.data and form_bug.bug_report.data: try: # Only send from a board or list # Do not send from an identity if config.DEFAULT_CHANS[0][ "address"] in daemon_com.get_all_chans(): address_from = config.DEFAULT_CHANS[0]["address"] elif daemon_com.get_all_chans(): address_from = list(daemon_com.get_all_chans().keys())[0] else: status_msg['status_message'].append( "Could not find address to send from. " "Join/Create a board or list and try again.") address_from = None alembic_version = Alembic.query.first().version_num message_compiled = "BitChan version: {}\n".format( config.VERSION_BITCHAN) message_compiled += "Database version: {} (should be {})\n\n".format( alembic_version, config.VERSION_ALEMBIC) message_compiled += "Message:\n\n{}".format( form_bug.bug_report.data) message_b64 = base64.b64encode( message_compiled.encode()).decode() ts = datetime.datetime.fromtimestamp( daemon_com.get_utc()).strftime('%Y-%m-%d %H:%M:%S') subject = "Bug Report {} ({})".format(config.VERSION_BITCHAN, ts) subject_b64 = base64.b64encode(subject.encode()).decode() if not status_msg['status_message']: if address_from: # Don't allow a message to send while Bitmessage is restarting allow_send = False timer = time.time() while not allow_send: if daemon_com.bitmessage_restarting() is False: allow_send = True if time.time() - timer > config.BM_WAIT_DELAY: logger.error( "Unable to send message: " "Could not detect Bitmessage running.") return time.sleep(1) if allow_send: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: return_str = api.sendMessage( config.BITCHAN_BUG_REPORT_ADDRESS, address_from, subject_b64, message_b64, 2, config.BM_TTL) if return_str: status_msg['status_title'] = "Success" status_msg['status_message'].append( "Sent. Thank you for your feedback. " "Send returned: {}".format( return_str)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) except Exception as err: status_msg['status_message'].append( "Could not send: {}".format(err)) logger.exception("Could not send bug report: {}".format(err)) if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" return render_template("pages/bug_report.html", form_bug=form_bug, replace_lt_gt=replace_lt_gt, settings=GlobalSettings.query.first(), status_msg=status_msg, themes=themes.themes)
def compose(address_from, address_to): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg from_all = [] form_msg = forms_mailbox.Compose() if address_from == "0": address_from = "" if address_to == "0": address_to = "" from_all.extend(daemon_com.get_identities().keys()) from_all.extend(daemon_com.get_all_chans().keys()) form_populate = session.get('form_populate', {}) status_msg = session.get('status_msg', {"status_message": []}) if request.method == 'GET': if 'form_populate' in session: session.pop('form_populate') if 'status_msg' in session: session.pop('status_msg') if request.method == 'POST': if form_msg.send.data: if not form_msg.to_address.data: status_msg['status_message'].append( "Must provide a To Address") if not form_msg.from_address.data: status_msg['status_message'].append( "Must provide a From Address") if not (3600 <= form_msg.ttl.data <= 2419200): status_msg['status_message'].append( "TTL must be between 3600 and 2419200") if not status_msg['status_message']: if form_msg.subject.data: subject = base64.b64encode( form_msg.subject.data.encode()).decode() else: subject = "" if form_msg.body.data: message = base64.b64encode( form_msg.body.data.encode()).decode() else: message = "" # Don't allow a message to send while Bitmessage is restarting allow_send = False timer = time.time() while not allow_send: if daemon_com.bitmessage_restarting() is False: allow_send = True if time.time() - timer > config.BM_WAIT_DELAY: logger.error("Unable to send message: " "Could not detect Bitmessage running.") return time.sleep(1) if allow_send: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: # TODO: message sends but results in error. Diagnose. status_msg['status_title'] = "Success" status_msg['status_message'].append( "Message sent to queue") try: return_str = api.sendMessage( form_msg.to_address.data, form_msg.from_address.data, subject, message, 2, form_msg.ttl.data) except Exception as err: if err.__str__( ) == "<Fault 21: 'Unexpected API Failure - too many values to unpack'>": return_str = "Error: API Failure (despite this error, the message probably still sent)" else: return_str = "Error: {}".format(err) if return_str: logger.info( "Send message from {} to {}. Returned: {}". format(form_msg.from_address.data, form_msg.to_address.data, return_str)) status_msg['status_message'].append( "Bitmessage returned: {}".format( return_str)) except Exception as err: logger.exception("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) if 'status_title' not in status_msg and status_msg['status_message']: status_msg['status_title'] = "Error" form_populate = { "to_address": form_msg.to_address.data, "from_address": form_msg.from_address.data, "ttl": form_msg.ttl.data, "subject": form_msg.subject.data, "body": form_msg.body.data, } session['form_populate'] = form_populate session['status_msg'] = status_msg if not address_from: address_from = "0" return redirect( url_for("routes_mail.compose", address_from=address_from, address_to="0")) return render_template("mailbox/compose.html", address_from=address_from, address_to=address_to, form_populate=form_populate, from_all=from_all, get_from_list_all=get_from_list_all, status_msg=status_msg)
def thread_steg(current_chan, thread_id): can_view, allow_msg = allowed_access(check_can_view=True) if not can_view: return allow_msg form_post = forms_board.Post() form_steg = forms_board.Steg() settings = GlobalSettings.query.first() chan = Chan.query.filter(Chan.address == current_chan).first() if len(thread_id) == 12: thread = Threads.query.filter(Threads.thread_hash_short == thread_id).first() else: thread = Threads.query.filter(Threads.thread_hash == thread_id).first() if not chan: return render_template("pages/404-board.html", board_address=current_chan) if not thread: return render_template("pages/404-thread.html", board_address=current_chan, thread_id=thread_id) board = { "current_chan": chan, "current_thread": thread, "messages": Messages } form_populate = session.get('form_populate', {}) status_msg = session.get('status_msg', {"status_message": []}) if request.method == 'GET': if 'form_populate' in session: session.pop('form_populate') if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': if form_post.start_download.data: can_download, allow_msg = allowed_access(check_can_download=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=current_chan) if not can_download and not board_list_admin: return allow_msg daemon_com.set_start_download(form_post.message_id.data) status_msg['status_title'] = "Success" status_msg['status_message'].append( "File download initialized in the background. Give it time to download.") elif form_post.submit.data: invalid_post = False if not form_post.validate(): for field, errors in form_post.errors.items(): if field == "csrf_token": invalid_post = True for error in errors: logger.error("Error in the {} field - {}".format( getattr(form_post, field).label.text, error)) if invalid_post: status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid") if not form_post.page_id.data: status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid ID") elif settings.enable_captcha and not captcha.validate(page_id=form_post.page_id.data): status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid Captcha") can_post, allow_msg = allowed_access(check_can_post=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=current_chan) if not can_post and not board_list_admin: return allow_msg if form_post.default_from_address.data: thread.default_from_address = form_post.from_address.data else: thread.default_from_address = None thread.save() status_msg, result, form_populate = post_message( form_post, form_steg, status_msg) session['form_populate'] = form_populate session['status_msg'] = status_msg return redirect(url_for("routes_board.thread_steg", current_chan=current_chan, thread_id=thread_id)) try: from_list = daemon_com.get_from_list(current_chan) except: return render_template("pages/404-board.html", board_address=current_chan) return render_template("pages/thread_steg.html", board=board, form_populate=form_populate, form_post=form_post, form_steg=form_steg, from_list=from_list, page_id=str(uuid.uuid4()), status_msg=status_msg, upload_sites=UploadSites)
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 block_address(chan_address, block_address, block_type): """Block address locally, on single board or across all boards""" 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=chan_address) if not global_admin and not board_list_admin: return allow_msg form_confirm = forms_board.Confirm() chan = Chan.query.filter(Chan.address == chan_address).first() board = { "current_chan": chan, "current_thread": None, } status_msg = {"status_message": []} if block_address in daemon_com.get_identities(): status_msg['status_message'].append("You cannot block your own identity") status_msg['status_title'] = "Error" elif request.method != 'POST' or not form_confirm.confirm.data: return render_template("pages/confirm.html", action="block_address", block_type=block_type, chan=chan, chan_address=chan_address, block_address=block_address) elif request.method == 'POST' and form_confirm.confirm.data: messages = Messages.query.filter( Messages.address_from == block_address).all() list_delete_message_ids = [] for message in messages: if block_type == "single_board" and message.thread.chan.address == chan_address: list_delete_message_ids.append(message.message_id) elif block_type == "global": if not global_admin: return allow_msg list_delete_message_ids.append(message.message_id) lf = LF() if lf.lock_acquire(config.LOCKFILE_MSG_PROC, to=60): try: # First, delete messages from database if list_delete_message_ids: for each_id in list_delete_message_ids: delete_post(each_id) daemon_com.signal_generate_post_numbers() # Allow messages to be deleted in bitmessage before allowing bitchan to rescan inbox time.sleep(1) except Exception as err: logger.error("Exception while deleting messages: {}".format(err)) finally: lf.lock_release(config.LOCKFILE_MSG_PROC) new_cmd = Command() new_cmd.do_not_send = True new_cmd.action = "block" new_cmd.action_type = "block_address" new_cmd.options = json.dumps({"block_address": block_address}) if block_type == "single_board": new_cmd.chan_address = chan_address elif block_type == "global": new_cmd.chan_address = "all" # global block (all boards) new_cmd.save() status_msg['status_title'] = "Success" status_msg['status_message'].append("Blocked address {}".format(block_address)) return render_template("pages/alert.html", board=board, status_msg=status_msg)
def board(current_chan, current_page): allowed, allow_msg = allowed_access(check_can_view=True) if not allowed: return allow_msg form_post = forms_board.Post() form_steg = forms_board.Steg() form_set = forms_board.SetChan() settings = GlobalSettings.query.first() chan = Chan.query.filter(Chan.address == current_chan).first() if not chan: return render_template("pages/404-board.html", board_address=current_chan) board = { "current_page": int(current_page), "current_chan": chan, "current_thread": None, "messages": Messages, "threads": Threads.query .filter(Threads.chan_id == chan.id) .order_by(Threads.timestamp_sent.desc()) } form_populate = session.get('form_populate', {}) status_msg = session.get('status_msg', {"status_message": []}) def get_threads_from_page(address, page): threads_sticky = [] stickied_hash_ids = [] thread_start = int((int(page) - 1) * settings.results_per_page_board) thread_end = int(int(page) * settings.results_per_page_board) - 1 chan_ = Chan.query.filter(Chan.address == address).first() # Find all threads remotely stickied admin_cmds = Command.query.filter( Command.chan_address == address, Command.action_type == "thread_options" ).order_by(Command.timestamp_utc.desc()).all() for each_adm in admin_cmds: try: options = json.loads(each_adm.options) except: options = {} if "sticky" in options and options["sticky"]: sticky_thread = Threads.query.filter( Threads.thread_hash == each_adm.thread_id).first() if sticky_thread: stickied_hash_ids.append(sticky_thread.thread_hash) threads_sticky.append(sticky_thread) # Find all thread locally stickied (and prevent duplicates) threads_sticky_db = Threads.query.filter( and_( Threads.chan_id == chan_.id, or_(Threads.stickied_local.is_(True)) )).order_by(Threads.timestamp_sent.desc()).all() for each_db_sticky in threads_sticky_db: if each_db_sticky.thread_hash not in stickied_hash_ids: threads_sticky.append(each_db_sticky) threads_all = Threads.query.filter( and_( Threads.chan_id == chan_.id, Threads.stickied_local.is_(False) )).order_by(Threads.timestamp_sent.desc()).all() threads = [] threads_count = 0 for each_thread in threads_sticky: if threads_count > thread_end: break if thread_start <= threads_count: threads.append(each_thread) threads_count += 1 for each_thread in threads_all: if each_thread.thread_hash in stickied_hash_ids: continue # skip stickied threads if threads_count > thread_end: break if thread_start <= threads_count: threads.append(each_thread) threads_count += 1 return threads if request.method == 'GET': if 'form_populate' in session: session.pop('form_populate') if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': 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 Message PGP Passphrase.") if form_set.set_pgp_passphrase_attach.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_attach.data: status_msg['status_message'].append("Attachment PGP passphrase required") elif len(form_set.pgp_passphrase_attach.data) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append("Attachment PGP passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(form_set.pgp_passphrase_attach.data))) else: chan.pgp_passphrase_attach = form_set.pgp_passphrase_attach.data chan.save() status_msg['status_title'] = "Success" status_msg['status_message'].append("Changed Attachment PGP Passphrase.") elif form_set.set_pgp_passphrase_steg.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_steg.data: status_msg['status_message'].append("Steg PGP passphrase required") elif len(form_set.pgp_passphrase_steg.data) > config.PGP_PASSPHRASE_LENGTH: status_msg['status_message'].append("Steg PGP passphrase longer than {}: {}".format( config.PGP_PASSPHRASE_LENGTH, len(form_set.pgp_passphrase_steg.data))) else: chan.pgp_passphrase_steg = form_set.pgp_passphrase_steg.data chan.save() status_msg['status_title'] = "Success" status_msg['status_message'].append("Changed Steg PGP Passphrase.") elif form_post.start_download.data: can_download, allow_msg = allowed_access(check_can_download=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=current_chan) if not can_download and not board_list_admin: return allow_msg daemon_com.set_start_download(form_post.message_id.data) status_msg['status_title'] = "Success" status_msg['status_message'].append( "File download initialized in the background. Give it time to download.") elif form_post.submit.data: invalid_post = False if not form_post.validate(): for field, errors in form_post.errors.items(): if field == "csrf_token": invalid_post = True for error in errors: logger.error("Error in the {} field - {}".format( getattr(form_post, field).label.text, error)) if invalid_post: status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid Token") if not form_post.page_id.data: status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid ID") elif settings.enable_captcha and not captcha.validate(page_id=form_post.page_id.data): status_msg['status_title'] = "Error" status_msg['status_message'].append("Invalid Captcha") can_post, allow_msg = allowed_access( check_can_post=True) board_list_admin, allow_msg = allowed_access( check_is_board_list_admin=True, check_admin_board=current_chan) if not can_post and not board_list_admin: return allow_msg if form_post.default_from_address.data: chan.default_from_address = form_post.from_address.data else: chan.default_from_address = None chan.save() status_msg, result, form_populate = post_message( form_post, form_steg, status_msg) session['form_populate'] = form_populate 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_board.board", current_chan=current_chan, current_page=current_page)) try: from_list = daemon_com.get_from_list(current_chan) except: return render_template("pages/404-board.html", board_address=current_chan) chan = Chan.query.filter(Chan.address == current_chan).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 if chan.pgp_passphrase_attach != config.PGP_PASSPHRASE_ATTACH: dict_join["pgp_attach"] = chan.pgp_passphrase_attach if chan.pgp_passphrase_steg != config.PGP_PASSPHRASE_STEG: dict_join["pgp_steg"] = chan.pgp_passphrase_steg passphrase_base64_with_pgp = base64.b64encode( json.dumps(dict_join).encode()).decode().replace("/", "&") return render_template("pages/board.html", board=board, form_populate=form_populate, form_post=form_post, from_list=from_list, get_threads_from_page=get_threads_from_page, page_id=str(uuid.uuid4()), passphrase_base64=passphrase_base64, passphrase_base64_with_pgp=passphrase_base64_with_pgp, status_msg=status_msg, upload_sites=UploadSites)
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 mailbox(ident_address, mailbox, page, msg_id): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg status_msg = {"status_message": []} messages = [] msg_selected = [] identities = daemon_com.get_identities() page = int(page) form_mail = forms_mailbox.Mailbox() if msg_id != "0": if mailbox == "inbox": lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: msg_selected = api.getInboxMessageById(msg_id, True) if "inboxMessage" in msg_selected: msg_selected = msg_selected["inboxMessage"][0] expires = get_msg_expires_time(msg_id) if expires: msg_selected["expires_time"] = expires except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) elif mailbox == "sent": lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: msg_selected = api.getSentMessageById(msg_id) if "sentMessage" in msg_selected: msg_selected = msg_selected["sentMessage"][0] expires = get_msg_expires_time(msg_id) if expires: msg_selected["expires_time"] = expires except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) if request.method == 'POST': settings = GlobalSettings.query.first() if ((form_mail.messages_per_mailbox_page.data or (form_mail.messages_per_mailbox_page.data and form_mail.set_per_page.data)) and form_mail.messages_per_mailbox_page.data != settings.messages_per_mailbox_page): settings.messages_per_mailbox_page = form_mail.messages_per_mailbox_page.data settings.save() elif form_mail.execute_bulk_action.data and form_mail.bulk_action.data: msg_ids = request.form.getlist("selected_msg") if form_mail.bulk_action.data == "delete": lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: for each_id in msg_ids: if mailbox == "inbox": api.trashInboxMessage(each_id) elif mailbox == "sent": api.trashSentMessage(each_id) except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) return redirect( url_for("routes_mail.mailbox", ident_address=ident_address, mailbox=mailbox, page="1", msg_id="0")) if form_mail.bulk_action.data in ["mark_read", "mark_unread"]: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: for each_id in msg_ids: api.getInboxMessageById( each_id, form_mail.bulk_action.data == "mark_read") except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) daemon_com.update_unread_mail_count(ident_address) return redirect( url_for("routes_mail.mailbox", ident_address=ident_address, mailbox=mailbox, page=page, msg_id=msg_id)) elif form_mail.reply.data and form_mail.message_id.data: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: msg_selected = api.getInboxMessageById( form_mail.message_id.data, True) if "inboxMessage" in msg_selected: msg_selected = msg_selected["inboxMessage"][0] form_populate = { "to_address": msg_selected["fromAddress"], "body": "\n\n\n------------------------------------------------------\n{}" .format(base64_decode(msg_selected["message"])) } if base64_decode( msg_selected["subject"]).startswith("Re:"): form_populate["subject"] = base64_decode( msg_selected["subject"]) else: form_populate["subject"] = "Re: {}".format( base64_decode(msg_selected["subject"])) session['form_populate'] = form_populate session['status_msg'] = status_msg except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) return redirect(url_for("routes_mail.compose", address_to="0")) elif form_mail.forward.data and form_mail.message_id.data: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: msg_selected = api.getInboxMessageById( form_mail.message_id.data, True) if "inboxMessage" in msg_selected: msg_selected = msg_selected["inboxMessage"][0] form_populate = { "body": "\n\n\n------------------------------------------------------\n{}" .format(base64_decode(msg_selected["message"])) } if base64_decode( msg_selected["subject"]).startswith("Fwd:"): form_populate["subject"] = base64_decode( msg_selected["subject"]) else: form_populate["subject"] = "Fwd: {}".format( base64_decode(msg_selected["subject"])) session['form_populate'] = form_populate session['status_msg'] = status_msg except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) return redirect(url_for("routes_mail.compose", address_to="0")) elif form_mail.delete.data and form_mail.message_id.data: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: api.trashMessage(form_mail.message_id.data) except Exception as err: logger.error("Error: {}".format(err)) finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) return redirect( url_for("routes_mail.mailbox", ident_address=ident_address, mailbox=mailbox, page=page, msg_id="0")) if ident_address != '0' and mailbox == "inbox": daemon_com.update_unread_mail_count(ident_address) total_mail_counts = {} unread_mail_counts = {} for each_identity in Identity.query.all(): unread_mail_counts[ each_identity.address] = each_identity.unread_messages total_mail_counts[each_identity.address] = each_identity.total_messages return render_template("mailbox/mailbox.html", base64_decode=base64_decode, get_messages_from_page=get_messages_from_page, ident_address=ident_address, identities=identities, mailbox=mailbox, msg_id=msg_id, msg_selected=msg_selected, messages=messages, page=page, status_msg=status_msg, timestamp_format=timestamp_format, total_mail_counts=total_mail_counts, unread_mail_counts=unread_mail_counts)
def address_book(): global_admin, allow_msg = allowed_access( check_is_global_admin=True) if not global_admin: return allow_msg form_addres_book = forms_settings.AddressBook() 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_addres_book.add.data: if not form_addres_book.label.data or not form_addres_book.address.data: status_msg['status_message'].append("Label and address required") if not status_msg['status_message']: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: label = base64.b64encode(form_addres_book.label.data.encode()).decode() try: return_str = api.addAddressBookEntry( form_addres_book.address.data, label) except Exception as e: if e: return_str = "Could not add to Address Book: {}".format(e) else: return_str = "Not a valid address?" if return_str: if "Added address" in return_str: new_add_book = AddressBook() new_add_book.address = form_addres_book.address.data new_add_book.label = form_addres_book.label.data new_add_book.save() daemon_com.refresh_address_book() status_msg['status_title'] = "Success" status_msg['status_message'].append( "Added Address Book entry {}".format( form_addres_book.label.data)) status_msg['status_message'].append( "Give the system a few seconds for the change to take effect.") else: status_msg['status_message'].append(return_str) else: status_msg['status_message'].append( "Error creating Address Book entry") finally: time.sleep(config.API_PAUSE) lf.lock_release(config.LOCKFILE_API) elif form_addres_book.rename.data: if not form_addres_book.add_label.data or not form_addres_book.address.data: status_msg['status_message'].append("Label and address required") if not status_msg['status_message']: add_book = AddressBook.query.filter( AddressBook.address == form_addres_book.address.data).first() if add_book: add_book.label = form_addres_book.add_label.data add_book.save() daemon_com.refresh_address_book() status_msg['status_title'] = "Success" status_msg['status_message'].append("Address Book entry renamed.") status_msg['status_message'].append( "Give the system a few seconds for the change to take effect.") elif form_addres_book.delete.data: add_book = None if not form_addres_book.address.data: status_msg['status_message'].append("Address required") else: add_book = AddressBook.query.filter( AddressBook.address == form_addres_book.address.data).first() if not form_confirm.confirm.data: return render_template("pages/confirm.html", action="delete_address_book", add_book=add_book) if not status_msg['status_message']: lf = LF() if lf.lock_acquire(config.LOCKFILE_API, to=config.API_LOCK_TIMEOUT): try: return_str = api.deleteAddressBookEntry(form_addres_book.address.data) if "Deleted address book entry" in return_str: if add_book: add_book.delete() daemon_com.refresh_address_book() status_msg['status_title'] = "Success" status_msg['status_message'].append("Address Book entry 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 Address Book entry: {}".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_address_book.address_book")) return render_template("pages/address_book.html", form_addres_book=form_addres_book, status_msg=status_msg)
def pgp(): global_admin, allow_msg = allowed_access(check_is_global_admin=True) if not global_admin: return allow_msg form_pgp = forms_settings.PGP() status_msg = session.get("status_msg", {"status_message": []}) gnupg_home = "/usr/local/bitchan/gnupg" if not os.path.exists(gnupg_home): os.mkdir(gnupg_home) gpg = gnupg.GPG(gnupghome=gnupg_home) private_keys = gpg.list_keys(True) public_keys = gpg.list_keys() private_key_ids = [] for each_key in private_keys: private_key_ids.append(each_key["keyid"]) public_key_ids = [] for each_key in public_keys: if each_key["keyid"] not in private_key_ids: public_key_ids.append(each_key["keyid"]) exported_public_keys = {} for each_pub_key in public_keys: exported_public_keys[each_pub_key["keyid"]] = gpg.export_keys( each_pub_key["keyid"]) if request.method == 'GET': if 'status_msg' in session: session.pop('status_msg') elif request.method == 'POST': if form_pgp.create_master_key.data: key_type = form_pgp.key_type_length.data.split(",")[0] key_length = int(form_pgp.key_type_length.data.split(",")[1]) input_data = gpg.gen_key_input(key_type=key_type, key_length=key_length, key_usage='encrypt, sign', name_comment=form_pgp.comment.data, expire_date=0, name_real=form_pgp.name.data, name_email=form_pgp.email.data, passphrase=form_pgp.passphrase.data) key = gpg.gen_key(input_data) status_msg['status_message'].append( "PGP key pair created: {}".format(key.fingerprint)) status_msg['status_title'] = "Success" elif form_pgp.delete_all.data: shutil.rmtree(gnupg_home) # for each_key in gpg.list_keys(True): # status_msg['status_message'].append("Delete Private Key {}: {}".format( # each_key["fingerprint"], # gpg.delete_keys(fingerprints=each_key["fingerprint"], # secret=True, # passphrase="PASS").status)) # for each_key in gpg.list_keys(): # status_msg['status_message'].append("Delete Public Key {}: {}".format( # each_key["fingerprint"], # gpg.delete_keys(fingerprints=each_key["fingerprint"]).status)) status_msg['status_message'].append("Deleted all keys") status_msg['status_title'] = "Success" 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_pgp.pgp")) return render_template("pages/pgp.html", exported_public_keys=exported_public_keys, private_keys=private_keys, public_key_ids=public_key_ids, public_keys=public_keys, status_msg=status_msg)