1#! /usr/bin/env python3
2#
3# Copyright (C) 2018 Garmin Ltd.
4#
5# SPDX-License-Identifier: GPL-2.0-only
6#
7
8import os
9import sys
10import logging
11import argparse
12import sqlite3
13import warnings
14
15warnings.simplefilter("default")
16
17sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(__file__)), "lib"))
18
19import hashserv
20from hashserv.server import DEFAULT_ANON_PERMS
21
22VERSION = "1.0.0"
23
24DEFAULT_BIND = "unix://./hashserve.sock"
25
26
27def main():
28    parser = argparse.ArgumentParser(
29        description="Hash Equivalence Reference Server. Version=%s" % VERSION,
30        formatter_class=argparse.RawTextHelpFormatter,
31        epilog="""
32The bind address may take one of the following formats:
33    unix://PATH         - Bind to unix domain socket at PATH
34    ws://ADDRESS:PORT   - Bind to websocket on ADDRESS:PORT
35    ADDRESS:PORT        - Bind to raw TCP socket on ADDRESS:PORT
36
37To bind to all addresses, leave the ADDRESS empty, e.g. "--bind :8686" or
38"--bind ws://:8686". To bind to a specific IPv6 address, enclose the address in
39"[]", e.g. "--bind [::1]:8686" or "--bind ws://[::1]:8686"
40
41Note that the default Anonymous permissions are designed to not break existing
42server instances when upgrading, but are not particularly secure defaults. If
43you want to use authentication, it is recommended that you use "--anon-perms
44@read" to only give anonymous users read access, or "--anon-perms @none" to
45give un-authenticated users no access at all.
46
47Setting "--anon-perms @all" or "--anon-perms @user-admin" is not allowed, since
48this would allow anonymous users to manage all users accounts, which is a bad
49idea.
50
51If you are using user authentication, you should run your server in websockets
52mode with an SSL terminating load balancer in front of it (as this server does
53not implement SSL). Otherwise all usernames and passwords will be transmitted
54in the clear. When configured this way, clients can connect using a secure
55websocket, as in "wss://SERVER:PORT"
56
57The following permissions are supported by the server:
58
59    @none       - No permissions
60    @read       - The ability to read equivalent hashes from the server
61    @report     - The ability to report equivalent hashes to the server
62    @db-admin   - Manage the hash database(s). This includes cleaning the
63                  database, removing hashes, etc.
64    @user-admin - The ability to manage user accounts. This includes, creating
65                  users, deleting users, resetting login tokens, and assigning
66                  permissions.
67    @all        - All possible permissions, including any that may be added
68                  in the future
69        """,
70    )
71
72    parser.add_argument(
73        "-b",
74        "--bind",
75        default=os.environ.get("HASHSERVER_BIND", DEFAULT_BIND),
76        help='Bind address (default $HASHSERVER_BIND, "%(default)s")',
77    )
78    parser.add_argument(
79        "-d",
80        "--database",
81        default=os.environ.get("HASHSERVER_DB", "./hashserv.db"),
82        help='Database file (default $HASHSERVER_DB, "%(default)s")',
83    )
84    parser.add_argument(
85        "-l",
86        "--log",
87        default=os.environ.get("HASHSERVER_LOG_LEVEL", "WARNING"),
88        help='Set logging level (default $HASHSERVER_LOG_LEVEL, "%(default)s")',
89    )
90    parser.add_argument(
91        "-u",
92        "--upstream",
93        default=os.environ.get("HASHSERVER_UPSTREAM", None),
94        help="Upstream hashserv to pull hashes from ($HASHSERVER_UPSTREAM)",
95    )
96    parser.add_argument(
97        "-r",
98        "--read-only",
99        action="store_true",
100        help="Disallow write operations from clients ($HASHSERVER_READ_ONLY)",
101    )
102    parser.add_argument(
103        "--db-username",
104        default=os.environ.get("HASHSERVER_DB_USERNAME", None),
105        help="Database username ($HASHSERVER_DB_USERNAME)",
106    )
107    parser.add_argument(
108        "--db-password",
109        default=os.environ.get("HASHSERVER_DB_PASSWORD", None),
110        help="Database password ($HASHSERVER_DB_PASSWORD)",
111    )
112    parser.add_argument(
113        "--anon-perms",
114        metavar="PERM[,PERM[,...]]",
115        default=os.environ.get("HASHSERVER_ANON_PERMS", ",".join(DEFAULT_ANON_PERMS)),
116        help='Permissions to give anonymous users (default $HASHSERVER_ANON_PERMS, "%(default)s")',
117    )
118    parser.add_argument(
119        "--admin-user",
120        default=os.environ.get("HASHSERVER_ADMIN_USER", None),
121        help="Create default admin user with name ADMIN_USER ($HASHSERVER_ADMIN_USER)",
122    )
123    parser.add_argument(
124        "--admin-password",
125        default=os.environ.get("HASHSERVER_ADMIN_PASSWORD", None),
126        help="Create default admin user with password ADMIN_PASSWORD ($HASHSERVER_ADMIN_PASSWORD)",
127    )
128
129    args = parser.parse_args()
130
131    logger = logging.getLogger("hashserv")
132
133    level = getattr(logging, args.log.upper(), None)
134    if not isinstance(level, int):
135        raise ValueError("Invalid log level: %s (Try ERROR/WARNING/INFO/DEBUG)" % args.log)
136
137    logger.setLevel(level)
138    console = logging.StreamHandler()
139    console.setLevel(level)
140    logger.addHandler(console)
141
142    read_only = (os.environ.get("HASHSERVER_READ_ONLY", "0") == "1") or args.read_only
143    if "," in args.anon_perms:
144        anon_perms = args.anon_perms.split(",")
145    else:
146        anon_perms = args.anon_perms.split()
147
148    server = hashserv.create_server(
149        args.bind,
150        args.database,
151        upstream=args.upstream,
152        read_only=read_only,
153        db_username=args.db_username,
154        db_password=args.db_password,
155        anon_perms=anon_perms,
156        admin_username=args.admin_user,
157        admin_password=args.admin_password,
158    )
159    server.serve_forever()
160    return 0
161
162
163if __name__ == "__main__":
164    try:
165        ret = main()
166    except Exception:
167        ret = 1
168        import traceback
169
170        traceback.print_exc()
171    sys.exit(ret)
172