Merge branch 'master' into cygwin
[re6stnet.git] / re6st-conf
1 #!/usr/bin/python
2 import argparse, atexit, errno, os, subprocess, sqlite3, sys, time
3 from OpenSSL import crypto
4 from re6st import registry, utils
5
6 def create(path, text=None, mode=0666):
7     fd = os.open(path, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)
8     try:
9         os.write(fd, text)
10     finally:
11         os.close(fd)
12
13 def loadCert(pem):
14     return crypto.load_certificate(crypto.FILETYPE_PEM, pem)
15
16 def main():
17     parser = argparse.ArgumentParser(
18         description="Setup script for re6stnet.",
19         formatter_class=utils.HelpFormatter)
20     _ = parser.add_argument
21     _('--registry', required=True, metavar='URL',
22         help="HTTP URL of the server delivering certificates.")
23     _('--is-needed', action='store_true',
24         help="Exit immediately after asking the registry CA. Status code is"
25              " non-zero if we're already part of the network, which means"
26              " re6st is already running or we're behind a re6st router.")
27     _('--ca-only', action='store_true',
28         help='Only fetch CA from registry and exit.')
29     _('-d', '--dir',
30         help="Directory where the key and certificate will be stored.")
31     _('-r', '--req', nargs=2, action='append', metavar=('KEY', 'VALUE'),
32         help="The registry only sets the Common Name of your certificate,"
33              " which actually encodes your allocated prefix in the network."
34              " You can repeat this option to add any field you want to its"
35              " subject.")
36     _('--email',
37         help="Email address where your token is sent. Use -r option if you"
38              " want to show an email in your certificate.")
39     _('--token', help="The token you received.")
40     _('--anonymous', action='store_true',
41         help="Request an anonymous certificate. No email is required but the"
42              " registry may deliver a longer prefix.")
43     config = parser.parse_args()
44     if config.dir:
45         os.chdir(config.dir)
46     conf_path = 're6stnet.conf'
47     ca_path = 'ca.crt'
48     cert_path = 'cert.crt'
49     key_path = 'cert.key'
50     dh_path = 'dh2048.pem'
51
52     # Establish connection with server
53     s = registry.RegistryClient(config.registry)
54
55     # Get CA
56     ca = s.getCa()
57     network = utils.networkFromCa(loadCert(ca))
58     if config.is_needed:
59         route, err = subprocess.Popen(('ip', '-6', '-o', 'route', 'get',
60                                        utils.ipFromBin(network)),
61                                       stdout=subprocess.PIPE).communicate()
62         sys.exit(err or route and
63             utils.binFromIp(route.split()[8]).startswith(network))
64
65     create(ca_path, ca)
66     if config.ca_only:
67         sys.exit()
68
69     # Generating dh file
70     if not os.access(dh_path, os.F_OK):
71         r = subprocess.call(('openssl', 'dhparam', '-out', dh_path, '2048'))
72         if r:
73             sys.exit(r)
74
75     req = crypto.X509Req()
76     try:
77         with open(cert_path) as f:
78             cert = loadCert(f.read())
79         components = dict(cert.get_subject().get_components())
80         components.pop('CN', None)
81     except IOError, e:
82         if e.errno != errno.ENOENT:
83             raise
84         components = {}
85     if config.req:
86         components.update(config.req)
87     subj = req.get_subject()
88     for k, v in components.iteritems():
89         if k == 'CN':
90             sys.exit("CN field is reserved.")
91         if v:
92             setattr(subj, k, v)
93
94     cert_fd = token_advice = None
95     try:
96         token = config.token
97         if config.anonymous:
98             if not (token is config.email is None):
99                 parser.error("--anonymous conflicts with --email/--token")
100             token = ''
101         elif not token:
102             if not config.email:
103                 config.email = raw_input('Please enter your email address: ')
104             s.requestToken(config.email)
105             token_advice = "Use --token to retry without asking a new token\n"
106             while not token:
107                 token = raw_input('Please enter your token: ')
108
109         try:
110             with open(key_path) as f:
111                 pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
112             key = None
113             print "Reusing existing key."
114         except IOError, e:
115             if e.errno != errno.ENOENT:
116                 raise
117             print "Generating 2048-bit key ..."
118             pkey = crypto.PKey()
119             pkey.generate_key(crypto.TYPE_RSA, 2048)
120             key = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
121             create(key_path, key, 0600)
122
123         req.set_pubkey(pkey)
124         req.sign(pkey, 'sha1')
125         req = crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)
126
127         # First make sure we can open certificate file for writing,
128         # to avoid using our token for nothing.
129         cert_fd = os.open(cert_path, os.O_CREAT | os.O_WRONLY, 0666)
130         print "Requesting certificate ..."
131         cert = s.requestCertificate(token, req)
132         if not cert:
133             token_advice = None
134             sys.exit("Error: invalid or expired token")
135     except:
136         if cert_fd is not None and not os.lseek(cert_fd, 0, os.SEEK_END):
137             os.remove(cert_path)
138         if token_advice:
139             atexit.register(sys.stdout.write, token_advice)
140         raise
141     os.write(cert_fd, cert)
142     os.ftruncate(cert_fd, len(cert))
143     os.close(cert_fd)
144
145     cert = loadCert(cert)
146     not_after = utils.notAfter(cert)
147     print("Setup complete. Certificate is valid until %s UTC"
148           " and will be automatically renewed after %s UTC" % (
149         time.asctime(time.gmtime(not_after)),
150         time.asctime(time.gmtime(not_after - registry.RENEW_PERIOD))))
151
152     if not os.path.lexists(conf_path):
153         create(conf_path, """\
154 registry %s
155 ca %s
156 cert %s
157 key %s
158 dh %s
159 # for udp only:
160 #pp 1194 udp
161 # increase re6stnet verbosity:
162 #verbose 3
163 # enable OpenVPN logging:
164 #ovpnlog
165 # increase OpenVPN verbosity:
166 #O--verb
167 #O3
168 """ % (config.registry, ca_path, cert_path, key_path, dh_path))
169         print "Sample configuration file created."
170
171     cn = utils.subnetFromCert(cert)
172     subnet = network + utils.binFromSubnet(cn)
173     print "Your subnet: %s/%u (CN=%s)" \
174         % (utils.ipFromBin(subnet), len(subnet), cn)
175
176 if __name__ == "__main__":
177     main()