feat: --show-eml outputs base64-encoded .eml; implies --no-send-email by default
This commit is contained in:
@@ -335,7 +335,7 @@ def create_note(content: str, base_url: str) -> str:
|
||||
# ============================================================
|
||||
|
||||
|
||||
def send_email(
|
||||
def build_mime_message(
|
||||
to_address: str,
|
||||
cn: str,
|
||||
one_time_url: str,
|
||||
@@ -343,9 +343,8 @@ def send_email(
|
||||
mail_from: str,
|
||||
subject: str,
|
||||
template_path: str,
|
||||
mail_binary: str,
|
||||
) -> None:
|
||||
"""Compose and send email with .ovpn attachment via msmtp/sendmail."""
|
||||
) -> email.mime.multipart.MIMEMultipart:
|
||||
"""Build the MIME message with .ovpn attachment; does not send."""
|
||||
config_name = os.path.basename(ovpn_path)
|
||||
body = Path(template_path).read_text().format(
|
||||
cn=cn, url=one_time_url, config_name=config_name,
|
||||
@@ -362,7 +361,22 @@ def send_email(
|
||||
email.encoders.encode_base64(part)
|
||||
part.add_header("Content-Disposition", f'attachment; filename="{config_name}"')
|
||||
msg.attach(part)
|
||||
return msg
|
||||
|
||||
|
||||
def send_email(
|
||||
to_address: str,
|
||||
cn: str,
|
||||
one_time_url: str,
|
||||
ovpn_path: str,
|
||||
mail_from: str,
|
||||
subject: str,
|
||||
template_path: str,
|
||||
mail_binary: str,
|
||||
) -> None:
|
||||
"""Compose and send email with .ovpn attachment via msmtp/sendmail."""
|
||||
msg = build_mime_message(to_address, cn, one_time_url, ovpn_path,
|
||||
mail_from, subject, template_path)
|
||||
proc = subprocess.Popen(
|
||||
[mail_binary, "-t"],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
@@ -984,13 +998,17 @@ class CursesApp:
|
||||
class CliRunner:
|
||||
"""Non-interactive runner for scripting / cron use."""
|
||||
|
||||
def create(self, cn: str, email_addr: str, send_email_flag: bool = True) -> None:
|
||||
self._issue(cn, email_addr, is_renewal=False, send_email_flag=send_email_flag)
|
||||
def create(self, cn: str, email_addr: str, send_email_flag: bool = True,
|
||||
show_eml: bool = False) -> None:
|
||||
self._issue(cn, email_addr, is_renewal=False,
|
||||
send_email_flag=send_email_flag, show_eml=show_eml)
|
||||
|
||||
def reissue(self, cn: str, email_addr: str, send_email_flag: bool = True) -> None:
|
||||
def reissue(self, cn: str, email_addr: str, send_email_flag: bool = True,
|
||||
show_eml: bool = False) -> None:
|
||||
# Prefer the caller-supplied email; fall back to stored metadata.
|
||||
final_email = email_addr or get_email(EASYRSA_PKI_DIR, cn)
|
||||
self._issue(cn, final_email, is_renewal=True, send_email_flag=send_email_flag)
|
||||
self._issue(cn, final_email, is_renewal=True,
|
||||
send_email_flag=send_email_flag, show_eml=show_eml)
|
||||
|
||||
def revoke(self, cn: str) -> None:
|
||||
print(f"Revoking {cn}…", file=sys.stderr)
|
||||
@@ -1014,7 +1032,7 @@ class CliRunner:
|
||||
print(f"CRL updated → {CRL_DEST_PATH}")
|
||||
|
||||
def _issue(self, cn: str, email_addr: str, is_renewal: bool,
|
||||
send_email_flag: bool = True) -> None:
|
||||
send_email_flag: bool = True, show_eml: bool = False) -> None:
|
||||
password = generate_password()
|
||||
|
||||
if is_renewal:
|
||||
@@ -1056,6 +1074,15 @@ class CliRunner:
|
||||
except CryptgeonError as exc:
|
||||
print(f"warning: Cryptgeon failed: {exc}", file=sys.stderr)
|
||||
|
||||
if show_eml and email_addr:
|
||||
try:
|
||||
msg = build_mime_message(email_addr, cn, one_time_url or password,
|
||||
ovpn_path, MAIL_FROM, MAIL_SUBJECT,
|
||||
EMAIL_TEMPLATE_PATH)
|
||||
print(base64.b64encode(msg.as_bytes()).decode())
|
||||
except Exception as exc:
|
||||
print(f"warning: could not build .eml: {exc}", file=sys.stderr)
|
||||
|
||||
if email_addr and send_email_flag:
|
||||
try:
|
||||
send_email(email_addr, cn, one_time_url or password, ovpn_path,
|
||||
@@ -1084,9 +1111,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument("--email", metavar="EMAIL",
|
||||
help="Email address (required for --create; optional for --reissue)")
|
||||
parser.add_argument("--send-email", dest="send_email", action="store_true",
|
||||
default=True, help="Send email after issuing cert (default)")
|
||||
default=None, help="Send email after issuing cert (default unless --show-eml)")
|
||||
parser.add_argument("--no-send-email", dest="send_email", action="store_false",
|
||||
help="Skip email delivery; print URL to stdout instead")
|
||||
parser.add_argument("--show-eml", action="store_true",
|
||||
help="Print the generated email as base64-encoded .eml to stdout "
|
||||
"(implies --no-send-email unless --send-email is also given)")
|
||||
return parser
|
||||
|
||||
|
||||
@@ -1099,12 +1129,17 @@ def main() -> None:
|
||||
parser = _build_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
# --show-eml switches the default to --no-send-email; explicit --send-email overrides.
|
||||
send_email_flag = args.send_email if args.send_email is not None else (not args.show_eml)
|
||||
|
||||
if args.create:
|
||||
if not args.email:
|
||||
parser.error("--email is required with --create")
|
||||
CliRunner().create(args.create, args.email, send_email_flag=args.send_email)
|
||||
CliRunner().create(args.create, args.email,
|
||||
send_email_flag=send_email_flag, show_eml=args.show_eml)
|
||||
elif args.reissue:
|
||||
CliRunner().reissue(args.reissue, args.email or "", send_email_flag=args.send_email)
|
||||
CliRunner().reissue(args.reissue, args.email or "",
|
||||
send_email_flag=send_email_flag, show_eml=args.show_eml)
|
||||
elif args.revoke:
|
||||
CliRunner().revoke(args.revoke)
|
||||
elif args.gen_crl:
|
||||
|
||||
Reference in New Issue
Block a user