#!/usr/bin/python import sys, os, cgi, commands, time import re import posixfile import ConfigParser import ipkg import smtplib import libxml2 import newtempfile import string config = '/etc/packman.conf' feeds_base = '/home/jamey/feeds' feeds_base = '/home/ftp/feeds' cgi.logfile = '/tmp/upload' tmp_dir = '/tmp' ipkgfind_url = 'http://ipkgfind.handhelds.org' update_package_list = 0 cp = ConfigParser.ConfigParser() cp.read(config) try: if cp.has_option('config', 'feeds_base'): feeds_base = cp.get('config', 'feeds_base') if cp.has_option('config', 'logfile'): cgi.logfile = cp.get('config', 'logfile') if cp.has_option('config', 'tmp_dir'): tmp_dir = cp.get('config', 'tmp_dir') if cp.has_option('config', 'update_index'): update_package_list = cp.get('config', 'update_index') except: pass cp = None logprefix = time.strftime('%c') + ' -- ' try: for k in ['REMOTE_ADDR']: if (os.environ.has_key(k)): logprefix = logprefix + ('%s' % (os.environ[k],)) except: pass def log(string): cgi.log(logprefix + ': ' + string) def filename_is_valid(filename): if re.match(r'^[+-._A-Za-z0-9]+$', filename): return 1 return 0 def copy(srcfilename, dstfilename): srcfile = open(srcfilename, 'r') dstfile = open(dstfilename, 'w') if not srcfile: return 1 if not dstfile: return 1 while (1): str = srcfile.read(512) if (str): nbytes = dstfile.write(str) else: break dstfile.close() srcfile.close() return 0 def make_index(feed_dir): os.chdir(feed_dir) f = posixfile.open(tmp_dir + "/Packages.lck", 'w') f.lock('w|') (rc, outtext) = commands.getstatusoutput('/usr/local/bin/ipkg-make-index . > /tmp/Packages') if (rc != 0): print ('Failed to create Packages file with error=%s and output=%s' % (rc,outtext)) else: commands.getstatusoutput('mv /tmp/Packages Packages') f.lock('u') f.close() print 'Updated Packages' return def update_index(feed_dir, pkgfilename): f = posixfile.open(tmp_dir + "/Packages.lck", 'w') f.lock('w|') (rc, outtext) = commands.getstatusoutput('ipkg-update-index %s %s' % (feed_dir, pkgfilename) ) if (rc != 0): print ('Failed to update Packages file with error=%s and output=%s' % (rc,outtext)) f.lock('u') f.close() print ('added %s to Packages' % (pkgfilename,)) return def announce_to_cia(uploader, feedname, filename, changenotice=None): msg = "From: commits@handhelds.org\r\nTo: commits@picogui.org\r\nContent-Type: text/plain;\r\nSubject: SendToChannels handhelds.org\r\n\r\n{light blue}" + filename +"{normal} uploaded to {light green}" + feedname + "{normal} by {orange}" + uploader + "{normal}\r\n" mailmsg = "From: " + uploader + "\r\nTo: familiar-updates@handhelds.org\r\nContent-Type: text/plain;\r\nSubject: " + filename + " uploaded to " + feedname + "\r\n\r\n\r\n" if changenotice: mailmsg = mailmsg + changenotice + "\r\n" server = smtplib.SMTP('localhost') server.sendmail('commits@handhelds.org', 'commits@picogui.org', msg) server.sendmail(uploader, 'familiar-updates@handhelds.org', mailmsg) server.quit() def fixup_filename(filename): if filename and filename[:8] == "C:\\Temp\\": filename = filename[8:] return filename def update_feed_rss(uploader, feedname, filename, upload_time): rss_filename = feeds_base + '/' + feedname + '/upload.rss' m = re.match(r'([^_]+)_([^_]+)_([^_]+).ipk', filename) if not m: return (pkg_name, pkg_version, pkg_arch) = m.groups() doc = libxml2.parseFile(rss_filename) items = [] c = doc.children.children while c: if c.name == 'item': items.append(c) c = c.next oldest = items[len(items)-1] items_parent = items[0].parent c = oldest.children while c: if c.name == 'title': c.children.setContent('%s (version %s arch %s by %s on %s)' % (pkg_name, pkg_version, pkg_arch, uploader, upload_time)) elif c.name == 'link': c.children.setContent(ipkgfind_url + '/details.phtml?package=' + pkg_name) else: pass c = c.next # now move c from end of list to head of list oldest.unlinkNode() items_parent.children.addPrevSibling(oldest) f = open(rss_filename, 'w') doc.dump(f) def handle_batch_upload(feeditem, batchitem, tarfileitem, sigfileitem, changenotice=None): global logprefix global update_package_list feedname = feeditem.value sigfilename = sigfileitem.filename batchfilename = batchitem.filename sigfilename = fixup_filename(sigfilename) batchfilename = fixup_filename(batchfilename) if not filename_is_valid(feedname): print ('%s is an invalid filename\n' % (feedname,)) log('invalid feedname %s' % (feedname,)) return 0 feed_dir = feeds_base + '/' + feedname + '/' if not filename_is_valid(batchfilename): print ('%s is an invalid filename\n' % (batchfilename,)) log('invalid filename %s' % (batchfilename,)) return 0 if not filename_is_valid(sigfilename): print ('%s is an invalid filename\n' % (sigfilename,)) log('invalid sigfilename %s' % (sigfilename,)) return 0 if (batchfilename == 'keyring.gpg' or sigfilename == 'keyring.gpg'): print 'not allowed to overwrite keyring' return 0 if (os.path.exists(feed_dir + '/' + batchfilename)): print ('%s already exists in feed directory' % (batchfilename,)) return 0 if (os.path.exists(feed_dir + '/' + sigfilename)): print ('%s already exists in feed directory' % (sigfilename,)) return 0 files = [] for line in string.split(batchitem.value, "\n"): fields = string.split(line) if not fields: continue md5sum = fields[0] filename = fields[1] if not filename_is_valid(filename): print ('%s is an invalid filename' % (filename,)) return 0 if (filename == 'keyring.gpg' or sigfilename == 'keyring.gpg'): print 'not allowed to overwrite keyring' return 0 if (os.path.exists(feed_dir + '/' + filename)): print ('%s already exists in feed directory' % (filename,)) return 0 files.append(filename) tmpdir = newtempfile.mkdtemp(); try: batchfile = open(tmpdir + "/" + batchfilename, 'w') batchfile.write(batchitem.value) batchfile.close() sigfile = open(tmpdir + "/" + sigfilename, 'w') sigfile.write(sigfileitem.value) sigfile.close() keyringfile = feed_dir + 'keyring.gpg' try: (exitstatus, outtext) = commands.getstatusoutput("/usr/bin/gpgv --keyring %s %s" % (keyringfile, tmpdir + '/' + sigfilename)) except: cgi.log('caught an exception in gpg') raise print '
' + outtext + '
' if (exitstatus != 0): log('/usr/bin/gpgv %s %s exitstatus=%d' % (filename, sigfilename, exitstatus,)) log(' ' + outtext) rc = exitstatus raise "failed" match = re.search("Good signature from.*\<(.+)\>", outtext) if (match): uploader = match.group(1) else: log('Could not extract uploader name from gpg output:') log(' ' + outtext) print "Something went wrong parsing output from gpg!" raise "failed" tarcmd = "tar --directory=%s -f - -x %s" % (tmpdir, string.join(files, " ")) tarpipe, tarout, tarerr = os.popen3(tarcmd) tarpipe.write(tarfileitem.value) tarpipe.close() while tarerr.readline() != "": pass tarout.close() tarerr.close() os.chdir(tmpdir) md5sumcmd = "md5sum -c %s" % (batchfilename,) try: (exitstatus, outtext) = commands.getstatusoutput(md5sumcmd) except: print "md5sum threw an exception" cgi.log('caught an exception in md5sum') raise if (exitstatus != 0): print "md5sum check failed" print outtext raise "failed" os.chdir("/") for filename in files: if (re.search(".*\.ipk$", filename)): ipk = ipkg.Package(tmpdir + "/" + filename) if not ipk: print '

Unable to parse ' + filename + '

' raise "failed" if not ipk.source: print '

Error: ' + filename + ' lacks a Source field in its control file.

' raise "failed" if not ipk.isdeb: print '

Error: ' + filename + ' is an old-format archive. Please upgrade ipkg-build.

' raise "failed" if ipk.filename_header: print '

Error: ' + filename + ' has a Filename header in its control file.

' raise "failed" for filename in files: rc = copy(tmpdir + "/" + filename, feed_dir + "/" + filename) if (rc != 0): print ('

error %s copying %s to %s' % (rc, tmpdir + "/" + filename, feed_dir + "/" + filename)) os.unlink(tmpdir + "/" + filename) log('Uploaded %s to %s' % (filename, feed_dir)) if (re.search(".*\.ipk$", filename)): announce_to_cia(uploader, feedname, filename) update_feed_rss(uploader, feedname, filename, time.strftime('%c')) copy(tmpdir + "/" + sigfilename, feed_dir + "/" + sigfilename) copy(tmpdir + "/" + batchfilename, feed_dir + "/" + batchfilename) except: print "Caught an exception" os.chdir("/") os.system("rm -r " + tmpdir) raise os.system("rm -r " + tmpdir) print "

Packages file now updated every 10 minutes by cron job

\n" return 1 def handle_upload(feeditem, fileitem, sigfileitem, srcfileitem, changenotice=None): global logprefix global update_package_list feedname = feeditem.value filename = fileitem.filename sigfilename = sigfileitem.filename srcfilename = srcfileitem.filename filename = fixup_filename(filename) sigfilename = fixup_filename(sigfilename) srcfilename = fixup_filename(srcfilename) # print "filename=%s\n" % (filename,) # print "sigfilename=%s\n" % (sigfilename,) # print "srcfilename=%s\n" % (srcfilename,) if not filename_is_valid(feedname): print ('%s is an invalid filename\n' % (feedname,)) log('invalid feedname %s' % (feedname,)) return 0 if not filename_is_valid(filename): print ('%s is an invalid filename\n' % (filename,)) log('invalid filename %s' % (filename,)) return 0 if not filename_is_valid(sigfilename): print ('%s is an invalid filename\n' % (sigfilename,)) log('invalid sigfilename %s' % (sigfilename,)) return 0 if srcfilename and not filename_is_valid(srcfilename): print ('%s is an invalid filename\n' % (srcfilename,)) log('invalid srcfilename %s' % (srcfilename,)) return 0 if (filename == 'keyring.gpg' or sigfilename == 'keyring.gpg'): print 'not allowed to overwrite keyring' return 0 feed_dir = feeds_base + '/' + feedname + '/' tmp_dir = "/tmp/" file = open(tmp_dir + filename, 'w') file.write(fileitem.value) file.close() sigfile = open(tmp_dir + sigfilename, 'w') sigfile.write(sigfileitem.value) sigfile.close() ipk = ipkg.Package(tmp_dir + filename) if not ipk: print '

Unable to parse ' + filename + '

' return 0 if not srcfilename and not ipk.source: print '

Error: ' + filename + ' lacks a Source field in its control file.

' return 0 if not ipk.isdeb: print '

Error: ' + filename + ' is an old-format archive. Please upgrade ipkg-build.

' return 0 if ipk.filename_header: print '

Error: ' + filename + ' has a Filename header in its control file.

' return 0 keyringfile = feed_dir + 'keyring.gpg' try: (exitstatus, outtext) = commands.getstatusoutput("/usr/bin/gpgv --keyring %s %s" % (keyringfile, tmp_dir + sigfilename)) except: cgi.log('caught an exception') return 0 print '
' + outtext + '
' if (exitstatus != 0): log('/usr/bin/gpgv %s %s exitstatus=%d' % (filename, sigfilename, exitstatus,)) log(' ' + outtext) rc = exitstatus if (rc != 0): return 0 match = re.search("Good signature from.*\<(.+)\>", outtext) if (match): uploader = match.group(1) announce_to_cia(uploader, feedname, filename) update_feed_rss(uploader, feedname, filename, time.strftime('%c')) rc = copy(tmp_dir + filename, feed_dir + filename) if (rc != 0): print ('

error %s copying %s to %s' % (rc, tmp_dir + filename, feed_dir + filename)) os.unlink(tmp_dir + filename) rc = copy(tmp_dir + sigfilename, feed_dir + sigfilename) if (rc != 0): print ('

error %s copying %s to %s' % (rc, tmp_dir + sigfilename, feed_dir + sigfilename)) os.unlink(tmp_dir + sigfilename) cgi.logfile = feed_dir + 'upload.log' log('uploaded %s the package %s %s' % (feedname, filename, sigfilename)) if changenotice: cgi.log('\t' + changenotice) if srcfilename: log('srcfile=' + srcfilename) overridefile = open(feed_dir + filename + ".override", 'w') overridefile.write('Source: ' + srcfilename + '\n') overridefile.close() srcfile= open(tmp_dir + srcfilename, 'w') srcfile.write(srcfileitem.value) srcfile.close() log('srcfile written to /tmp') rc = copy(tmp_dir + srcfilename, feed_dir + srcfilename) log('copied srcfile to feed, rc=%s' % (rc,)) if (rc != 0): print ('

error %s copying %s to %s' % (rc, tmp_dir + sigfilename, feed_dir + sigfilename)) os.unlink(tmp_dir + srcfilename) log('uploaded %s the source file %s' % (feedname, srcfilename)) print "

Packages file now updated every 10 minutes by cron job

\n" # remove the tmp files return 1 def main(): global update_package_list print "Content-type: text/html\n" form = cgi.FieldStorage() print '' print 'Handhelds.Org Package Manager -- upload' print '

Handhelds.Org Package Manager -- upload

' if form and form.has_key("batchfilename") and form.has_key("signaturefilename") and form.has_key("datafilename"): print '' if form.has_key('update_index'): update_package_list = form["update_index"] if form.has_key('changenotice'): changenotice= form['changenotice'] else: changenotice=None if handle_batch_upload(form["feedname"], form["batchfilename"], form["datafilename"], form["signaturefilename"], changenotice): print '

Upload completed successfully' print ('

Updated Packages File' % (form["feedname"].value, )) else: print '

Upload failed' elif form and form.has_key("filename") and form.has_key("signaturefilename") and form.has_key("sourcefilename"): print '

' if form.has_key('update_index'): update_package_list = form["update_index"] if form.has_key('changenotice'): changenotice= form['changenotice'] else: changenotice=None if handle_upload(form["feedname"], form["filename"], form["signaturefilename"],form["sourcefilename"], changenotice): print '

Upload completed successfully' print ('

Updated Packages File' % (form["feedname"].value, )) else: print '

Upload failed' elif form and form.has_key("feedname") and form.has_key("update_index"): # feed_dir = feeds_base + '/' + form["feedname"].value + '/' # make_index(feed_dir) print "Packages file now updated hourly by cron job at 11 after the hour\n" else: print """

Signing Packages

The following command will generate the file foo.ipk.asc, containing an ASCII signature of the package foo.ipk:
           gpg -sb --armor foo.ipk
       

See Jamey's announcement of this package upload utility for some more details.

Package to Upload

Feed Name
Filename
OpenPGP Signature Filename
Sources Filename - Optional
Change Notice - Optional

Please make sure that your package's control file has a Source field.

The Source field is intended to allow automated retrieval of the source package from which your package was built. If you are uploading a source package along with your binary package, the Source field should contain the name of the source package. Otherwise, it should contain one of the following:

""" print '' print '' main()