import repository from arizona
[raven.git] / lib / arizona-lib / arizonacurl.py
1 """
2 <Program Name>
3    arizonacurl
4
5 <Started>
6   Oct 20, 2006
7
8 <Author>
9    Programmed by Jeremy Plichta
10
11 <Purpose>
12    Fetch a users pubkey files and conf files using curl
13 """
14
15 import arizonaconfig
16 import arizonageneral
17 import arizonareport
18 import arizonacrypt
19 import storkrepodl
20 import storkrepolist
21 import storkusername
22 import tempfile
23 import os
24 import sys
25 import shutil
26
27
28 #           [option, long option,    variable,     action,        data,     default,                            metavar, description]
29 """arizonaconfig
30    options=[
31             ["-C",   "--configfile", "configfile", "store",       "string", "/usr/local/stork/etc/stork.conf", "FILE",   "use a different config file (/usr/local/stork/etc/stork.conf is the default)"],
32             ["",  "--certificate","certificate","store",       "string", "/usr/local/stork/var/certificates/nr06.certificate", "STRING",    "use a different certificate for downloading conf files from repository. the default is /usr/local/stork/var/certificates/nr06.certificate"],
33             ["",  "--repositorypath","repositorypath","store", "string", "https://stork-repository.cs.arizona.edu/user-upload/", "STRING",    "use a different repository to download configuration files from. the default is https://stork-repository.cs.arizona.edu/user-upload/"],
34             ["",  "--insecure","insecure","store_true",       None, False, None,    "do not use https for transfering configuration files from the repository using curl"],
35             ["",     "--publickeyfile", "publickeyfile", "store", "string", "",      "file",     "use this public key file to determine which configuration files to use.  Recommended: /usr/local/stork/var/keys/[username].publickey"],
36             ["",     "--requiresignedconf", "requiresignedconf", "store_true", None, False,      None,     "require downloaded configuration files to be signed. Default: False."]]
37    includes=[]
38 """
39
40
41
42
43
44
45 def fetch_conf():
46     """
47     <Purpose>
48         This function will attempt to download the users public key
49         and configuration file from the stork repository (or whatever
50         repository they specified using the repository option
51
52     <Arguments>
53         None
54
55     <Exceptions>
56         None
57
58     <Side Effects>
59         temp files will be created in /tmp and the the users
60         public key file will be replaced. The users configuration
61         file will also be replaced with the downloaded one if it
62         is succesfull
63
64         if the path to the specied config file does not exist
65         it will be created
66
67     <Returns>
68       True - if we loaded a new conf file and should reload the
69              options
70      False - if we did not replace the configuration file
71     """
72
73     # try to get the certificate and repositorypath options
74     certificate = arizonaconfig.get_option("certificate")
75     reppath     = arizonaconfig.get_option("repositorypath")
76     useinsecure = arizonaconfig.get_option("insecure")
77     conffile = arizonaconfig.get_option("configfile")
78     restartFlag = False
79     foundNewFile = False
80
81     # filename to use for tmpconf file (this is the one that we will
82     # download using curl
83     (tmpconffd, tmpconffile) = tempfile.mkstemp(suffix="arizonacurl.temp")
84     os.close(tmpconffd)
85
86     # Try finally block to remove the tempfile regardless of outcome
87     try:
88       if not useinsecure and not certificate:
89           arizonareport.send_error(1, "no certificate specified")
90           return False
91       if not useinsecure and not os.path.isfile(certificate):
92           arizonareport.send_error(1, "could not open certificate: "+certificate+" while trying to initiate curl transfer to download configuration file. Use --insecure to transfer without a certificate")
93           return False
94
95       # we're assuming at this point that storkrepolist hasn't been initialized
96       # yet
97       assert(not storkrepolist.glo_initialized)
98
99       storkrepodl.sync(False, maskList=["conf.tar.bz2"])
100
101       storkrepolist.init(False, maskList=["conf.tar.bz2"])
102
103       # SMB: make sure we only find config files named after the slicename,
104       # not after the username
105       storkusername.build_key_database(reset=True, ignore_username=True)
106
107       result = storkrepolist.find_file_kind("conf", "stork.conf")
108
109       # SMB: the remainer of stork will use files named after the username,
110       # so reset the key database that we messed with above
111       storkusername.reset_key_database()
112
113       if not result or result[0] == None:
114           arizonareport.send_error(3, "Unable to a locate signed conf file in the repository.")
115           # if we didn't find a signed file and we are requiring one, then stop here
116           if arizonaconfig.get_option("requiresignedconf"):
117               arizonareport.send_error(3, "Config options indicate only signed configuration files allowed. Will not look for unsigned conf files.")
118               return False
119       else:
120           arizonareport.send_error(3, "Found signed config file: " + str(result[0]))
121           try:
122              arizonacrypt.XML_retrieve_originalfile_from_signedfile(result[0], tmpconffile)
123              foundNewFile = True
124           except TypeError:
125              arizonareport.send_error(3, "Unable to extract unsigned file from the signed config file that was found.")
126              return False
127
128       # we didn't find a signed file, so look for an (old-style) unsigned file
129       # this if block could be removed if/when support for unsigned conf files is removed
130       if not foundNewFile:
131           # check to make sure that the repository path is / terminated
132           if reppath[-1] != '/':
133               reppath += '/'
134
135           slicename = arizonageneral.getslicename()
136           if not slicename:
137               arizonareport.send_error(1, "unable to download legacy conf file due to no slice name")
138               return False
139
140           file = reppath + "conf/" + slicename + ".stork.conf"
141
142           # ensure that the directory exists
143           condfir   = None
144           lastslash = conffile.rfind("/")
145           if lastslash > 0:
146              try:
147                 confdir = conffile[:lastslash]
148                 if not os.path.exists(confdir):
149                    os.makedirs(confdir)
150              except (OSError, IndexError):
151                 pass
152
153           arizonareport.send_out(3,"[INFO] Attempting to download unsigned configurations from: "+file)
154
155           if useinsecure:
156               execstring = "curl --insecure -w '%{http_code}' " + file + " -o "+tmpconffile
157           else:
158               execstring = "curl --cacert "+certificate+" -w '%{http_code}' " + file + " -o "+tmpconffile
159           out, err, status = arizonageneral.popen5(execstring)
160           if len(out) < 1 or out[0] != "200":
161               arizonareport.send_error(3,"[INFO]: I was unable to download your configuration file from: "+file+"[USING DEFAULT CONF FILE]: "+conffile)
162           else:
163               foundNewFile = True
164               # JRP: putting in code to get rid of any of the old conffiles
165               if os.path.isfile(conffile+".old"):
166                   os.system("rm -f "+conffile+"*.old* 2>/dev/null")
167
168       # if we found a new conf file, we'll see if we should install it
169       if foundNewFile:
170
171           # if there's an existing conf file, only overwrite it if the downloaded one is different
172           if os.path.isfile(conffile):
173
174               # added a try block to catch any IOError that might
175               # be raised by the get_fn_hash function, which
176               # shouldnt happen unless something really goes wrong,
177               # ex: the conffile or .tmpconffile dont exist (maybe
178               # there was a problem saving or accessing the tmpconffile?
179               try:
180                  curhash  = arizonacrypt.get_fn_hash(conffile)
181                  newhash  = arizonacrypt.get_fn_hash(tmpconffile)
182               except IOError:
183                  arizonareport.send_error(3, "There was an error while comparing your old configuration file")
184
185                  # try to figure out why this happened
186                  if not os.path.isfile(tmpconffile):
187                     arizonareport.send_error(3, "There was an error while getting the hash of: "+tmpconffile+\
188                                                 ", that file does not seem to exist.")
189                  elif not arizonageneral.valid_fn(tmpconffile):
190                     arizonareport.send_error(3, "There was an error while getting the hash of: "+tmpconffile+\
191                                                 ", that file exists but cannot be read from.")
192
193                  arizonareport.send_error(3, "Aborting update of "+conffile+". It will remain unchanged.")
194                  return False
195
196               if curhash != newhash:
197                   arizonareport.send_error(3, "Downloaded conf file is different than current file, replacing current file.")
198                   restartFlag = True
199                   move_file(conffile)
200                   shutil.copy(tmpconffile,conffile)
201               else:
202                   arizonareport.send_error(3, "Downloaded conf file is the same as the current file.")
203
204           # there's no existing conf file
205           else:
206               restartFlag = True
207               shutil.copy(tmpconffile,conffile)
208
209     # clean up the temp file
210     finally:
211        os.remove(tmpconffile)
212
213     return restartFlag
214
215
216
217
218 def fetch_pubkey():
219     """
220     <Purpose>
221          This function will attempt to download the users public key
222          from the stork repository (or whatever
223          repository they specified using the repository option
224
225     <Side Effects>
226          temp files will be created in /tmp and the the users
227          legacy public key file will be replaced.
228
229     <Returns>
230        True - if we loaded a new public key and need to rebuild key database
231       False - if we did not replace the public key
232     """
233    
234     certificate = arizonaconfig.get_option("certificate")
235     reppath     = arizonaconfig.get_option("repositorypath")
236     useinsecure = arizonaconfig.get_option("insecure")
237
238     restartFlag = False
239     
240     #check to see if the certificate exists
241     if not useinsecure and not os.path.isfile(certificate):
242         arizonareport.send_error(1,"could not open certificate: "+certificate+" while trying to initiate curl transfer to download public key file. Use --insecure to transfer without a certificate")
243         return False
244
245     slicename = arizonageneral.getslicename()
246     if not slicename:
247        arizonareport.send_error(1, "unable to download legacy pubkey due to no slice name")
248        return False
249
250     file = reppath + "pubkeys/" + slicename + ".publickey"
251
252     arizonareport.send_out(3,"[INFO] Attempting to download your custom public key from: "+file)
253
254     pubkey = arizonaconfig.get_option("legacypublickey")
255     if (not pubkey) or (pubkey=="None"):
256        arizonareport.send_out(0, "legacy public keys are disabled")
257        return False
258
259     #ensure that the directory exists
260     keydir = None
261     lastslash = pubkey.rfind("/")
262     if lastslash > 0:
263        try:
264           keydir = pubkey[:lastslash]
265           if not os.path.exists(keydir):
266              os.makedirs(keydir)
267        except (OSError, IndexError):
268           pass
269
270     # Generate a temp to hold the public key we'll d/l
271     (tmppubkeyfd, tmppubkeyfile) = tempfile.mkstemp(suffix="arizonacurlpub.temp")
272     os.close(tmppubkeyfd)
273
274     # Try finally block to remove the temp file
275     try:
276        if useinsecure:
277            execstring = "curl --insecure -w '%{http_code}' " + file + " -o "+ tmppubkeyfile
278        else:
279            execstring = "curl --cacert "+certificate+" -w '%{http_code}' " + file + " -o "+tmppubkeyfile
280        out, err, status = arizonageneral.popen5(execstring)
281
282        if len(out) < 1 or out[0] != "200":
283            arizonareport.send_error(3,"[INFO]: I was unable to download your public key from: "+file+" . If you would like to upload one please go to http://quiver.cs.arizona.edu/testphp/upload.php. After you upload your public for your slice it will be automatically distributed when stork starts up. [USING DEFAULT PUBKEY]: "+pubkey)
284        else:
285            # JRP: putting in code to get rid of any of the old conffiles
286            if os.path.isfile(pubkey+".old"):
287               os.system("rm -f "+pubkey+"*.old* 2>/dev/null")
288
289            # move the public key file to its new location
290            if pubkey != "":
291                # I dont think there is a option for key location: at least
292                # not that I could find.. Maybe I need to look harder.
293                # We really shouldn't be hardcoding paths in here.
294                if not os.path.isdir("/usr/local/stork/var/keys"):
295                    os.mkdir("/usr/local/stork/var/keys")
296
297                # check to see if the pub key has changed
298                if os.path.isfile(pubkey):
299                   curhash  = arizonacrypt.get_fn_hash(pubkey)
300                   newhash  = arizonacrypt.get_fn_hash(tmppubkeyfile)
301                   if not curhash == newhash:
302                       restartFlag = True
303                       #back up the file if it has changed
304                       move_file(pubkey)
305                       shutil.copy(tmppubkeyfile, pubkey)
306                else:
307                   # there was no existing pubkey file
308                   restartFlag = True
309                   shutil.copy(tmppubkeyfile, pubkey)
310
311     finally:
312        # clean up the downloaded file
313        try:
314           os.unlink(tmppubkeyfile)
315        except OSError:
316           arizonareport.send_error(3, "[INFO] Could not remove temporary file `" + str(tmppubkeyfile) + "'")
317
318     return restartFlag
319
320
321
322
323 def move_file(fn):
324     """Renames file fn to have a period and number appended to it, incrementing
325        the number as needed so as to not overwrite existing files."""
326
327     lastind = fn.rfind(".")
328     num   = 0
329     try:
330        num = int( fn[lastind+1:] )
331        first = fn[:lastind]
332     except ValueError:
333        first = fn
334     
335     # [jsamuel] when this gets to backup number 6, this always overwrites number 6, so
336     # why bother keeping more than one backup since after a while it will just be one anyways?
337     if os.path.exists(first+"."+str(num+1)) and num<5:
338         move_file(first + "." + str(num+1))
339
340     shutil.move(fn,first+"."+str(num+1))
341
342
343
344 # Start main
345 '''if __name__ == "__main__":
346    try:
347       # use error reporting tool
348       #storkerror.init_error_reporting("stork.py")
349
350       # get command line and config file options
351       args = arizonaconfig.init_options("arizona_curl.py", configfile_optvar="configfile", version="2.0")
352       main();
353    except KeyboardInterrupt:
354       arizonareport.send_out(0, "Exiting via keyboard interrupt...")
355       sys.exit(0)
356 '''
357