Skip to content

Commit b4eda7a

Browse files
Add token refresher daemon
1 parent 5df4c29 commit b4eda7a

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed

refresh_token_daemon.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env python3
2+
"""
3+
refresh_token_daemon.py
4+
5+
Periodically fetch an auth token and write to a file (used by sqlmap --eval).
6+
Configure via env vars or edit the defaults below.
7+
"""
8+
9+
import os
10+
import sys
11+
import time
12+
import json
13+
import tempfile
14+
import traceback
15+
from datetime import datetime
16+
from typing import Optional
17+
18+
import requests
19+
20+
# === CONFIG (edit here or set env vars) ===
21+
AUTH_URL = os.getenv("AUTH_URL", "https://target.example.com/auth")
22+
AUTH_BODY_JSON = os.getenv("AUTH_BODY_JSON") # example: '{"username":"me","password":"pwd"}'
23+
if AUTH_BODY_JSON is None:
24+
AUTH_BODY = {"username": "youruser", "password": "yourpass"}
25+
else:
26+
try:
27+
AUTH_BODY = json.loads(AUTH_BODY_JSON)
28+
except Exception:
29+
print("ERROR: AUTH_BODY_JSON not valid JSON", file=sys.stderr)
30+
AUTH_BODY = {}
31+
32+
DEFAULT_OUT_UNIX = "/tmp/current_token.txt"
33+
DEFAULT_OUT_WIN = r"C:\temp\current_token.txt"
34+
TOKEN_OUT = os.getenv("TOKEN_OUT", DEFAULT_OUT_UNIX if os.name != "nt" else DEFAULT_OUT_WIN)
35+
36+
TOKEN_JSON_KEY = os.getenv("TOKEN_JSON_KEY", "access_token") # set to "" to use raw body
37+
REFRESH_TTL = int(os.getenv("REFRESH_TTL", "50")) # seconds token considered fresh
38+
REFRESH_FREQ = int(os.getenv("REFRESH_FREQ", "10")) # daemon wake frequency (s)
39+
40+
REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "15"))
41+
MAX_RETRIES = int(os.getenv("MAX_RETRIES", "3"))
42+
RETRY_BACKOFF = float(os.getenv("RETRY_BACKOFF", "2.0"))
43+
44+
def log(msg: str, err: bool = False):
45+
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
46+
out = sys.stderr if err else sys.stdout
47+
print(f"[{ts}] {msg}", file=out)
48+
out.flush()
49+
50+
# Cross-platform file lock
51+
if os.name == "nt":
52+
import msvcrt
53+
class FileLock:
54+
def __init__(self, path): self.fp = open(path, "a+b")
55+
def acquire(self):
56+
try:
57+
msvcrt.locking(self.fp.fileno(), msvcrt.LK_NBLCK, 1); return True
58+
except OSError: return False
59+
def release(self):
60+
try: self.fp.seek(0); msvcrt.locking(self.fp.fileno(), msvcrt.LK_UNLCK, 1)
61+
except Exception: pass
62+
def close(self):
63+
try: self.fp.close()
64+
except Exception: pass
65+
else:
66+
import fcntl
67+
class FileLock:
68+
def __init__(self, path): self.fp = open(path, "a+")
69+
def acquire(self):
70+
try:
71+
fcntl.flock(self.fp.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB); return True
72+
except IOError: return False
73+
def release(self):
74+
try: fcntl.flock(self.fp.fileno(), fcntl.LOCK_UN)
75+
except Exception: pass
76+
def close(self):
77+
try: self.fp.close()
78+
except Exception: pass
79+
80+
def ensure_dir_for_file(path: str):
81+
d = os.path.dirname(path)
82+
if d and not os.path.exists(d):
83+
os.makedirs(d, exist_ok=True)
84+
85+
def atomic_write(path: str, data: str):
86+
ensure_dir_for_file(path)
87+
dirpath = os.path.dirname(path) or "."
88+
fd, tmp_path = tempfile.mkstemp(dir=dirpath)
89+
try:
90+
with os.fdopen(fd, "w") as tmpf:
91+
tmpf.write(data); tmpf.flush(); os.fsync(tmpf.fileno())
92+
os.replace(tmp_path, path)
93+
except Exception:
94+
log("ERROR during atomic write: " + traceback.format_exc(), err=True)
95+
try:
96+
if os.path.exists(tmp_path): os.remove(tmp_path)
97+
except Exception: pass
98+
99+
def read_token_file_if_recent(path: str, ttl: int) -> Optional[str]:
100+
try:
101+
if os.path.exists(path):
102+
mtime = os.path.getmtime(path)
103+
age = time.time() - mtime
104+
if age <= ttl:
105+
with open(path, "r") as f: return f.read().strip()
106+
except Exception: pass
107+
return None
108+
109+
def fetch_token_from_api() -> Optional[str]:
110+
attempt = 0; delay = 1.0
111+
while attempt < MAX_RETRIES:
112+
attempt += 1
113+
try:
114+
log(f"Attempting auth request (attempt {attempt}) to {AUTH_URL}")
115+
resp = requests.post(AUTH_URL, json=AUTH_BODY, timeout=REQUEST_TIMEOUT)
116+
resp.raise_for_status()
117+
if TOKEN_JSON_KEY:
118+
try:
119+
obj = resp.json(); token = obj.get(TOKEN_JSON_KEY)
120+
if token: return token.strip()
121+
else: log("Token JSON key not found; falling back to raw body", err=True)
122+
except ValueError:
123+
log("Response not JSON; falling back to raw body", err=True)
124+
text = resp.text.strip()
125+
return text if text else None
126+
except Exception as e:
127+
log(f"Auth request failed: {e}", err=True)
128+
if attempt < MAX_RETRIES:
129+
time.sleep(delay); delay *= RETRY_BACKOFF
130+
return None
131+
132+
def main():
133+
lockfile_path = TOKEN_OUT + ".lock"
134+
lock = FileLock(lockfile_path)
135+
log("Starting refresh_token_daemon")
136+
log(f"AUTH_URL: {AUTH_URL}")
137+
log(f"TOKEN_OUT: {TOKEN_OUT}")
138+
log(f"TOKEN_JSON_KEY: {TOKEN_JSON_KEY!r}")
139+
log(f"REFRESH_TTL: {REFRESH_TTL}s REFRESH_FREQ: {REFRESH_FREQ}s")
140+
ensure_dir_for_file(TOKEN_OUT)
141+
try:
142+
while True:
143+
existing = read_token_file_if_recent(TOKEN_OUT, REFRESH_TTL)
144+
if existing:
145+
log(f"Token is fresh (<= {REFRESH_TTL}s). Sleeping {REFRESH_FREQ}s.")
146+
time.sleep(REFRESH_FREQ); continue
147+
got_lock = lock.acquire()
148+
if not got_lock:
149+
log("Another process refreshing token; waiting."); time.sleep(REFRESH_FREQ); continue
150+
try:
151+
existing = read_token_file_if_recent(TOKEN_OUT, REFRESH_TTL)
152+
if existing:
153+
log("Token written by another process while waiting for lock; skipping fetch."); continue
154+
token = fetch_token_from_api()
155+
if token:
156+
atomic_write(TOKEN_OUT, token)
157+
log("Token refreshed and written successfully.")
158+
else:
159+
log("Failed to fetch token after retries; will retry later.", err=True)
160+
finally:
161+
lock.release()
162+
time.sleep(REFRESH_FREQ)
163+
except KeyboardInterrupt:
164+
log("Interrupted by user; exiting.")
165+
except Exception:
166+
log("Unexpected error: " + traceback.format_exc(), err=True)
167+
finally:
168+
try: lock.close()
169+
except Exception: pass
170+
171+
if __name__ == "__main__":
172+
main()

0 commit comments

Comments
 (0)