Skip to content

Instantly share code, notes, and snippets.

@oddstr13
Last active May 2, 2019 16:42
Show Gist options
  • Save oddstr13/d19c4517fe8d6e441cf48a0f08f8316b to your computer and use it in GitHub Desktop.
Save oddstr13/d19c4517fe8d6e441cf48a0f08f8316b to your computer and use it in GitHub Desktop.
$ sudo systemctl stop jellyfin
$ ./jellyfin-login-fix.py
---------
User oddstr13
Account has 5 failed login attempts.
Account is disabled.
PIN code is set.
Local PIN login is enabled.
Restore user? [Y]n 
ERROR: No write access to /etc/jellyfin/users/7b2664944327497a9e2b200138f22671/config.xml, /etc/jellyfin/users/7b2664944327497a9e2b200138f22671/policy.xml or /var/lib/jellyfin/data/users.db
Try running this script as the jellyfin user:
sudo -u jellyfin ./jellyfin-login-fix.py

$ sudo -u jellyfin ./jellyfin-login-fix.py
---------
User oddstr13
Account has 5 failed login attempts.
Account is disabled.
PIN code is set.
Local PIN login is enabled.
Restore user? [Y]n 
Reset password? y[N] 
Enabling account...
Resetting login attempts...
Disabling local PIN login...
Clearing PIN code...

$ sudo -u jellyfin ./jellyfin-login-fix.py
---------
User oddstr13
Reset password? y[N] y
Password: 
Changing user password...

$ sudo systemctl start jellyfin
#!/usr/bin/env python3
# Copyright (c) Oddstr13
# License: MIT - https://opensource.org/licenses/MIT
import json
import sqlite3
import os
import sys
import base64
import xml.dom.minidom
import getpass
import hashlib
import pwd
XMLPATH = "/etc/jellyfin/users/"
DBFILE = "/var/lib/jellyfin/data/users.db"
def decodeGUID(b):
return base64.b16encode(bytes([b[3], b[2], b[1], b[0], b[5], b[4], b[7], b[6]]) + b[8:]).decode("utf-8").lower()
def prompt(s, default=None):
yes = ["y", "yes", "true", "1"]
no = ["n", "no", "false", "0"]
if default is None:
q = "y/n"
elif default:
q = "[Y]n"
else:
q = "y[N]"
while True:
r = input("{}? {} ".format(s, q)).lower().strip()
if default is None:
if r in yes:
return True
if r in no:
return False
elif default:
if not r or r in yes:
return True
if r in no:
return False
else:
if r in yes:
return True
if not r or r in no:
return False
print("Answer Yes or No!")
if __name__ == "__main__":
with sqlite3.connect(DBFILE) as conn:
cur = conn.cursor()
users = cur.execute('SELECT * FROM LocalUsersv2').fetchall()
for id, bguid, data in users:
guid = decodeGUID(bytes(bguid))
if type(data) is bytes:
data = data.decode("utf-8")
data = json.loads(data)
pdir = os.path.join(XMLPATH, guid)
policy_xml = os.path.join(pdir, "policy.xml")
with open(policy_xml, "r") as fh:
policy = xml.dom.minidom.parse(fh)
config_xml = os.path.join(pdir, "config.xml")
with open(config_xml, "r") as fh:
config = xml.dom.minidom.parse(fh)
is_disabled = policy.getElementsByTagName('IsDisabled')[0].firstChild.data == "true"
login_attempts = int(policy.getElementsByTagName('InvalidLoginAttemptCount')[0].firstChild.data)
user_pass = data.get('Password')
user_name = data.get('Name')
user_pin = data.get('EasyPassword')
enable_local_password = config.getElementsByTagName('EnableLocalPassword')[0].firstChild.data == "true"
print("---------")
print("User {}".format(user_name))
if login_attempts:
print("Account has {} failed login attempts.".format(login_attempts))
if is_disabled:
print("Account is disabled.")
if user_pin:
print("PIN code is set.")
if enable_local_password:
print("Local PIN login is enabled.")
do_restore = False
if login_attempts or is_disabled or user_pin or enable_local_password:
if prompt("Restore user", True):
do_restore = True
if not os.access(policy_xml, os.W_OK) or not os.access(config_xml, os.W_OK) or not os.access(DBFILE, os.W_OK):
print("ERROR: No write access to {}, {} or {}".format(config_xml, policy_xml, DBFILE))
print("Try running this script as the jellyfin user:")
print("sudo -u {} {}".format(pwd.getpwuid(os.stat(config_xml).st_uid).pw_name, ' '.join(sys.argv)))
exit(100)
new_pass = None
if prompt("Reset password", False):
if not os.access(DBFILE, os.W_OK):
print("ERROR: No write access to {}".format(DBFILE))
print("Try running this script as the jellyfin user:")
print("sudo -u {} {}".format(pwd.getpwuid(os.stat(config_xml).st_uid).pw_name, ' '.join(sys.argv)))
exit(100)
new_pass = "$SHA1${}".format(hashlib.sha1(getpass.getpass().encode("utf-8")).hexdigest().upper())
if do_restore:
if login_attempts or is_disabled:
if is_disabled:
print("Enabling account...")
policy.getElementsByTagName('IsDisabled')[0].firstChild.data = "false"
if login_attempts:
print("Resetting login attempts...")
policy.getElementsByTagName('InvalidLoginAttemptCount')[0].firstChild.data = "0"
with open(policy_xml, "w") as fh:
policy.writexml(fh)
if enable_local_password:
print("Disabling local PIN login...")
config.getElementsByTagName('EnableLocalPassword')[0].firstChild.data = "false"
with open(config_xml, "w") as fh:
config.writexml(fh)
if user_pin:
print("Clearing PIN code...")
data.pop('EasyPassword')
if new_pass:
print("Changing user password...")
data["Password"] = new_pass
if (do_restore and user_pin) or new_pass:
cur.execute("UPDATE LocalUsersv2 SET data = :data WHERE id=:id", {'data':json.dumps(data), 'id': id})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment