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