dput.py 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. #! /usr/bin/python2
  2. # -*- coding: utf-8; -*-
  3. #
  4. # dput/dput.py
  5. # Part of ‘dput’, a Debian package upload toolkit.
  6. #
  7. # This is free software, and you are welcome to redistribute it under
  8. # certain conditions; see the end of this file for copyright
  9. # information, grant of license, and disclaimer of warranty.
  10. """ dput — Debian package upload tool. """
  11. import ConfigParser
  12. import email.parser
  13. from hashlib import md5, sha1
  14. import importlib
  15. import os
  16. import os.path
  17. import pkgutil
  18. import pwd
  19. import re
  20. import signal
  21. import stat
  22. import string
  23. import subprocess
  24. import sys
  25. import textwrap
  26. from . import crypto
  27. from .helper import dputhelper
  28. app_library_path = os.path.dirname(__file__)
  29. debug = 0
  30. def import_upload_functions():
  31. """ Import upload method modules and make them available. """
  32. upload_methods = {}
  33. package_name = "methods"
  34. modules_path = os.path.join(app_library_path, package_name)
  35. modules_found = [
  36. name for (__, name, ispkg) in
  37. pkgutil.iter_modules([modules_path])
  38. if not ispkg]
  39. if debug:
  40. sys.stdout.write("D: modules_found: %r\n" % modules_found)
  41. for module_name in modules_found:
  42. module = importlib.import_module("{package}.{module}".format(
  43. package=".".join(["dput", package_name]),
  44. module=module_name))
  45. if debug:
  46. sys.stdout.write("D: Module: %s (%r)\n" % (module_name, module))
  47. method_name = module_name
  48. if debug:
  49. sys.stdout.write("D: Method name: %s\n" % method_name)
  50. upload_methods[method_name] = module.upload
  51. return upload_methods
  52. def parse_changes(chg_fd):
  53. """ Parse the changes file. """
  54. check = chg_fd.read(5)
  55. if check != '-----':
  56. chg_fd.seek(0)
  57. else:
  58. # found a PGP header, gonna ditch the next 3 lines
  59. # eat the rest of the line
  60. chg_fd.readline()
  61. # Hash: SHA1
  62. chg_fd.readline()
  63. # empty line
  64. chg_fd.readline()
  65. if not chg_fd.readline().find('Format') != -1:
  66. chg_fd.readline()
  67. changes_text = chg_fd.read()
  68. changes = email.parser.HeaderParser().parsestr(changes_text)
  69. if 'files' not in changes:
  70. raise KeyError("No Files field in upload control file")
  71. for a in changes['files'].strip().split('\n'):
  72. if len(a.split()) != 5:
  73. sys.stderr.write("Invalid Files line in .changes:\n %s\n" % a)
  74. sys.exit(1)
  75. return changes
  76. def read_configs(extra_config, debug):
  77. """ Read configuration settings from config files.
  78. :param extra_config: Filesystem path of config file to read.
  79. :param debug: If true, enable debugging output.
  80. :return: The resulting `ConfigParser` instance.
  81. Read config files in this order:
  82. * If specified on the command line, only read `extra_config`.
  83. * Otherwise, read ‘/etc/dput.cf’ then ‘~/.dput.cf’.
  84. The config parser will layer values.
  85. """
  86. config = ConfigParser.ConfigParser()
  87. config.set('DEFAULT', 'login', 'username')
  88. config.set('DEFAULT', 'method', 'scp')
  89. config.set('DEFAULT', 'hash', 'md5')
  90. config.set('DEFAULT', 'allow_unsigned_uploads', '0')
  91. config.set('DEFAULT', 'allow_dcut', '0')
  92. config.set('DEFAULT', 'distributions', '')
  93. config.set('DEFAULT', 'allowed_distributions', '')
  94. config.set('DEFAULT', 'run_lintian', '0')
  95. config.set('DEFAULT', 'run_dinstall', '0')
  96. config.set('DEFAULT', 'check_version', '0')
  97. config.set('DEFAULT', 'scp_compress', '0')
  98. config.set('DEFAULT', 'default_host_main', '')
  99. config.set('DEFAULT', 'post_upload_command', '')
  100. config.set('DEFAULT', 'pre_upload_command', '')
  101. config.set('DEFAULT', 'ssh_config_options', '')
  102. config.set('DEFAULT', 'passive_ftp', '1')
  103. config.set('DEFAULT', 'progress_indicator', '0')
  104. config.set('DEFAULT', 'delayed', '')
  105. if extra_config:
  106. config_files = (extra_config,)
  107. else:
  108. config_files = ('/etc/dput.cf', os.path.expanduser("~/.dput.cf"))
  109. fd = None
  110. for config_file in config_files:
  111. try:
  112. fd = open(config_file)
  113. except IOError as e:
  114. if debug:
  115. sys.stderr.write(
  116. "%s: %s, skipping\n" % (e.strerror, config_file))
  117. continue
  118. if debug:
  119. sys.stdout.write(
  120. "D: Parsing Configuration File %s\n" % config_file)
  121. try:
  122. config.readfp(fd)
  123. except ConfigParser.ParsingError as e:
  124. sys.stderr.write("Error parsing config file:\n%s\n" % str(e))
  125. sys.exit(1)
  126. fd.close()
  127. if fd is None:
  128. sys.stderr.write(
  129. "Error: Could not open any configfile, tried %s\n"
  130. % (', '.join(config_files)))
  131. sys.exit(1)
  132. # only check for fqdn and incoming dir, rest have reasonable defaults
  133. error = 0
  134. for section in config.sections():
  135. if config.get(section, 'method') == 'local':
  136. config.set(section, 'fqdn', 'localhost')
  137. if (
  138. not config.has_option(section, 'fqdn') and
  139. config.get(section, 'method') != 'local'):
  140. sys.stderr.write(
  141. "Config error: %s must have a fqdn set\n" % section)
  142. error = 1
  143. if not config.has_option(section, 'incoming'):
  144. sys.stderr.write(
  145. "Config error: %s must have an incoming directory set\n"
  146. % section)
  147. error = 1
  148. if error:
  149. sys.exit(1)
  150. return config
  151. hexStr = string.hexdigits
  152. def hexify_string(string):
  153. """ Convert a string of bytes to hexadecimal text representation. """
  154. char = ''
  155. ord_func = ord if isinstance(string, str) else int
  156. for c in string:
  157. char += hexStr[(ord_func(c) >> 4) & 0xF] + hexStr[ord_func(c) & 0xF]
  158. return char
  159. def checksum_test(filename, hash_name):
  160. """ Get the hex string for the hash of a file's content.
  161. :param filename: Path to the file to read.
  162. :param hash_name: Name of the hash to use.
  163. :return: The computed hash value, as hexadecimal text.
  164. Currently supports md5, sha1. ripemd may come in the future.
  165. """
  166. try:
  167. file_to_test = open(filename, 'rb')
  168. except IOError:
  169. sys.stdout.write("Can't open %s\n" % filename)
  170. sys.exit(1)
  171. if hash_name == 'md5':
  172. hash_type = md5
  173. else:
  174. hash_type = sha1
  175. check_obj = hash_type()
  176. while 1:
  177. data = file_to_test.read(65536)
  178. if len(data) == 0:
  179. break
  180. check_obj.update(data)
  181. file_to_test.close()
  182. checksum = hexify_string(check_obj.digest())
  183. return checksum
  184. def check_upload_variant(changes, debug):
  185. """ Check if this is a binary_upload only or not. """
  186. binary_upload = 0
  187. if 'architecture' in changes:
  188. arch = changes['architecture']
  189. if debug:
  190. sys.stdout.write("D: Architecture: %s\n" % arch)
  191. if arch.find('source') < 0:
  192. if debug:
  193. sys.stdout.write("D: Doing a binary upload only.\n")
  194. binary_upload = 1
  195. return binary_upload
  196. def verify_signature(
  197. host, changes_file_path, dsc_file_path,
  198. config, check_only, unsigned_upload, binary_upload, debug):
  199. """ Check the signature on the two files given via function call.
  200. :param host: Configuration host name.
  201. :param changes_file_path: Filesystem path of upload control file.
  202. :param dsc_file_path: Filesystem path of source control file.
  203. :param config: `ConfigParser` instance for this application.
  204. :param check_only: If true, no upload is requested.
  205. :param unsigned_upload: If true, allow an unsigned upload.
  206. :param binary_upload: If true, this upload excludes source.
  207. :param debug: If true, enable debugging output.
  208. :return: ``None``.
  209. """
  210. def assert_good_signature_or_exit(path):
  211. """ Assert the signature on the file at `path` is good. """
  212. try:
  213. with open(path) as infile:
  214. crypto.check_file_signature(infile)
  215. except Exception as exc:
  216. if isinstance(exc, crypto.gpgme.GpgmeError):
  217. sys.stdout.write("{}\n".format(exc))
  218. sys.exit(1)
  219. else:
  220. raise
  221. if debug:
  222. sys.stdout.write(
  223. "D: upload control file: {}\n".format(changes_file_path))
  224. sys.stdout.write(
  225. "D: source control file: {}\n".format(dsc_file_path))
  226. if ((check_only or config.getboolean(host, 'allow_unsigned_uploads') == 0)
  227. and not unsigned_upload):
  228. sys.stdout.write("Checking signature on .changes\n")
  229. assert_good_signature_or_exit(changes_file_path)
  230. if not binary_upload:
  231. sys.stdout.write("Checking signature on .dsc\n")
  232. assert_good_signature_or_exit(dsc_file_path)
  233. def source_check(changes, debug):
  234. """ Check if a source tarball has to be included in the package or not. """
  235. include_orig = include_tar = 0
  236. if 'version' in changes:
  237. version = changes['version']
  238. if debug:
  239. sys.stdout.write("D: Package Version: %s\n" % version)
  240. # versions with a dash in them are for non-native only
  241. if version.find('-') == -1:
  242. # debian native
  243. include_tar = 1
  244. else:
  245. if version.find(':') > 0:
  246. if debug:
  247. sys.stdout.write("D: Epoch found\n")
  248. epoch, version = version.split(':', 1)
  249. pos = version.rfind('-')
  250. upstream_version = version[0:pos]
  251. debian_version = version[pos + 1:]
  252. if debug:
  253. sys.stdout.write(
  254. "D: Upstream Version: %s\n" % upstream_version)
  255. sys.stdout.write("D: Debian Version: %s\n" % debian_version)
  256. if (
  257. debian_version == '0.1' or debian_version == '1'
  258. or debian_version == '1.1'):
  259. include_orig = 1
  260. else:
  261. include_tar = 1
  262. return (include_orig, include_tar)
  263. def verify_files(
  264. path, filename, host,
  265. config, check_only, check_version, unsigned_upload, debug):
  266. """ Run some tests on the files to verify that they are in good shape.
  267. :param path: Directory path of the upload control file.
  268. :param filename: Filename of the upload control file.
  269. :param host: Configuration host name.
  270. :param config: `ConfigParser` instance for this application.
  271. :param check_only: If true, no upload is requested.
  272. :param check_version: If true, check the package version
  273. before upload.
  274. :param unsigned_upload: If true, allow an unsigned upload.
  275. :param debug: If true, enable debugging output.
  276. :return: A collection of filesystem paths of all files to upload.
  277. """
  278. file_seen = include_orig_tar_gz = include_tar_gz = binary_only = 0
  279. files_to_upload = []
  280. name_of_file = filename
  281. change_file = os.path.join(path, name_of_file)
  282. if debug:
  283. sys.stdout.write(
  284. "D: Validating contents of changes file %s\n" % change_file)
  285. try:
  286. chg_fd = open(change_file, 'r')
  287. except IOError:
  288. sys.stdout.write("Can't open %s\n" % change_file)
  289. sys.exit(1)
  290. changes = parse_changes(chg_fd)
  291. chg_fd.close
  292. # Find out if it's a binary only upload or not
  293. binary_upload = check_upload_variant(changes, debug)
  294. if binary_upload:
  295. dsc_file = ''
  296. else:
  297. dsc_file = None
  298. for file in changes['files'].strip().split('\n'):
  299. # filename only
  300. filename = file.split()[4]
  301. if filename.find('.dsc') != -1:
  302. if debug:
  303. sys.stdout.write("D: dsc-File: %s\n" % filename)
  304. dsc_file = os.path.join(path, filename)
  305. if not dsc_file:
  306. sys.stderr.write("Error: no dsc file found in sourceful upload\n")
  307. sys.exit(1)
  308. # Run the check to verify that the package has been tested.
  309. try:
  310. if config.getboolean(host, 'check_version') == 1 or check_version:
  311. version_check(path, changes, debug)
  312. except ConfigParser.NoSectionError as e:
  313. sys.stderr.write("Error in config file:\n%s\n" % str(e))
  314. sys.exit(1)
  315. # Verify the signature of the maintainer
  316. verify_signature(
  317. host, change_file, dsc_file,
  318. config, check_only, unsigned_upload, binary_upload, debug)
  319. # Check the sources
  320. (include_orig_tar_gz, include_tar_gz) = source_check(changes, debug)
  321. # Check md5sum and the size
  322. file_list = changes['files'].strip().split('\n')
  323. hash_name = config.get('DEFAULT', 'hash')
  324. for line in file_list:
  325. (check_sum, size, section, priority, file) = line.split()
  326. file_to_upload = os.path.join(path, file)
  327. if debug:
  328. sys.stdout.write("D: File to upload: %s\n" % file_to_upload)
  329. if checksum_test(file_to_upload, hash_name) != check_sum:
  330. if debug:
  331. sys.stdout.write(
  332. "D: Checksum from .changes: %s\n" % check_sum)
  333. sys.stdout.write(
  334. "D: Generated Checksum: %s\n" %
  335. checksum_test(file_to_upload, hash_name))
  336. sys.stdout.write(
  337. "Checksum doesn't match for %s\n" % file_to_upload)
  338. sys.exit(1)
  339. else:
  340. if debug:
  341. sys.stdout.write(
  342. "D: Checksum for %s is fine\n" % file_to_upload)
  343. if os.stat(file_to_upload)[stat.ST_SIZE] != int(size):
  344. if debug:
  345. sys.stdout.write("D: size from .changes: %s\n" % size)
  346. sys.stdout.write(
  347. "D: calculated size: %s\n"
  348. % os.stat(file_to_upload)[stat.ST_SIZE])
  349. sys.stdout.write(
  350. "size doesn't match for %s\n" % file_to_upload)
  351. files_to_upload.append(file_to_upload)
  352. # Check filenames
  353. for file in files_to_upload:
  354. if file[-12:] == '.orig.tar.gz' and not include_orig_tar_gz:
  355. if debug:
  356. sys.stdout.write("D: Filename: %s\n" % file)
  357. sys.stdout.write("D: Suffix: %s\n\n" % file[-12:])
  358. sys.stdout.write(
  359. "Package includes an .orig.tar.gz file although"
  360. " the debian revision suggests\n"
  361. "that it might not be required."
  362. " Multiple uploads of the .orig.tar.gz may be\n"
  363. "rejected by the upload queue management software.\n")
  364. elif (
  365. file[-7:] == '.tar.gz' and not include_tar_gz
  366. and not include_orig_tar_gz):
  367. if debug:
  368. sys.stdout.write("D: Filename: %s\n" % file)
  369. sys.stdout.write("D: Suffix: %s\n" % file[-7:])
  370. sys.stdout.write(
  371. "Package includes a .tar.gz file although"
  372. " the version suggests that it might\n"
  373. "not be required."
  374. " Multiple uploads of the .tar.gz may be rejected by the\n"
  375. "upload queue management software.\n")
  376. distribution = changes.get('distribution')
  377. allowed_distributions = config.get(host, 'allowed_distributions')
  378. if distribution and allowed_distributions:
  379. if debug:
  380. sys.stdout.write(
  381. "D: Checking: distribution %s matches %s\n"
  382. % (distribution, allowed_distributions))
  383. if not re.match(allowed_distributions, distribution):
  384. raise dputhelper.DputUploadFatalException(
  385. "Error: uploading files for distribution %s to %s"
  386. " not allowed."
  387. % (distribution, host))
  388. if debug:
  389. sys.stdout.write("D: File to upload: %s\n" % change_file)
  390. files_to_upload.append(change_file)
  391. return files_to_upload
  392. def print_config(config, debug):
  393. """ Print the configuration and exit. """
  394. sys.stdout.write("\n")
  395. config.write(sys.stdout)
  396. sys.stdout.write("\n")
  397. def create_upload_file(package, host, fqdn, path, files_to_upload, debug):
  398. """ Write the log file for the upload.
  399. :param package: File name of package to upload.
  400. :param host: Configuration host name.
  401. :param fqdn: Fully-qualified domain name of the remote host.
  402. :param path: Filesystem path of the upload control file.
  403. :param debug: If true, enable debugging output.
  404. :return: ``None``.
  405. The upload log file is named ‘basename.hostname.upload’, where
  406. “basename” is the package file name without suffix, and
  407. “hostname” is the name of the host as specified in the
  408. configuration file.
  409. For example, uploading ‘foo_1.2.3-1_xyz.deb’ to host ‘bar’
  410. will be logged to ‘foo_1.2.3-1_xyz.bar.upload’.
  411. The upload log file is written to the
  412. directory containing the upload control file.
  413. """
  414. # only need first part
  415. base = os.path.splitext(package)[0]
  416. logfile_name = os.path.join(path, base + '.' + host + '.upload')
  417. if debug:
  418. sys.stdout.write("D: Writing logfile: %s\n" % logfile_name)
  419. try:
  420. if os.access(logfile_name, os.R_OK):
  421. logfile_fd = open(logfile_name, 'a')
  422. else:
  423. logfile_fd = open(logfile_name, 'w')
  424. except IOError:
  425. sys.stderr.write("Could not write %s\n" % logfile_name)
  426. sys.exit(1)
  427. for file in files_to_upload:
  428. entry_for_logfile = (
  429. 'Successfully uploaded ' + os.path.basename(file) +
  430. ' to ' + fqdn + ' for ' + host + '.\n')
  431. logfile_fd.write(entry_for_logfile)
  432. logfile_fd.close()
  433. def run_lintian_test(changes_file):
  434. """ Run lintian on the changes file and stop if it finds errors. """
  435. if os.access(changes_file, os.R_OK):
  436. if os.access("/usr/bin/lintian", os.R_OK):
  437. old_signal = signal.signal(signal.SIGPIPE, signal.SIG_DFL)
  438. sys.stdout.write("Package is now being checked with lintian.\n")
  439. if dputhelper.check_call(
  440. ['lintian', '-i', changes_file]
  441. ) != dputhelper.EXIT_STATUS_SUCCESS:
  442. sys.stdout.write(
  443. "\n"
  444. "Lintian says this package is not compliant"
  445. " with the current policy.\n"
  446. "Please check the current policy and your package.\n"
  447. "Also see lintian documentation about overrides.\n")
  448. sys.exit(1)
  449. else:
  450. signal.signal(signal.SIGPIPE, old_signal)
  451. return 0
  452. else:
  453. sys.stdout.write(
  454. "lintian is not installed, skipping package test.\n")
  455. else:
  456. sys.stdout.write("Can't read %s\n" % changes_file)
  457. sys.exit(1)
  458. def guess_upload_host(path, filename, config):
  459. """ Guess the host where the package should be uploaded to.
  460. :param path: Directory path of the upload control file.
  461. :param filename: Filename of the upload control file.
  462. :param config: `ConfigParser` instance for this application.
  463. :return: The hostname determined for this upload.
  464. This is based on information from the upload control
  465. (‘*.changes’) file.
  466. """
  467. non_us = 0
  468. distribution = ""
  469. dist_re = re.compile(r'^Distribution: (.*)')
  470. name_of_file = filename
  471. changes_file = os.path.join(path, name_of_file)
  472. try:
  473. changes_file_fd = open(changes_file, 'r')
  474. except IOError:
  475. sys.stdout.write("Can't open %s\n" % changes_file)
  476. sys.exit(1)
  477. lines = changes_file_fd.readlines()
  478. for line in lines:
  479. match = dist_re.search(line)
  480. if match:
  481. distribution = match.group(1)
  482. # Try to guess a host based on the Distribution: field
  483. if distribution:
  484. for section in config.sections():
  485. host_dists = config.get(section, 'distributions')
  486. if not host_dists:
  487. continue
  488. for host_dist in host_dists.split(','):
  489. if distribution == host_dist.strip():
  490. if debug:
  491. sys.stdout.write(
  492. "D: guessing host %s"
  493. " based on distribution %s\n"
  494. % (section, host_dist))
  495. return section
  496. if len(config.get('DEFAULT', 'default_host_main')) != 0:
  497. sys.stdout.write(
  498. "Trying to upload package to %s\n"
  499. % config.get('DEFAULT', 'default_host_main'))
  500. return config.get('DEFAULT', 'default_host_main')
  501. else:
  502. sys.stdout.write(
  503. "Trying to upload package to ftp-master"
  504. " (ftp.upload.debian.org)\n")
  505. return "ftp-master"
  506. def dinstall_caller(filename, host, fqdn, login, incoming, debug):
  507. """ Run ‘dinstall’ for the package on the remote host.
  508. :param filename: Debian package filename to install.
  509. :param host: Configuration host name.
  510. :param fqdn: Fully-qualified domain name of the remote host.
  511. :param login: Username for login to the remote host.
  512. :param incoming: Filesystem path on remote host for incoming
  513. packages.
  514. :param debug: If true, enable debugging output.
  515. :return: ``None``.
  516. Run ‘dinstall’ on the remote host in test mode, and present
  517. the output to the user.
  518. This is so the user can see if the package would be installed
  519. or not.
  520. """
  521. command = [
  522. 'ssh', '%s@%s' % (login, fqdn),
  523. 'cd', '%s' % incoming,
  524. ';', 'dinstall', '-n', '%s' % filename]
  525. if debug:
  526. sys.stdout.write(
  527. "D: Logging into %s@%s:%s\n" % (login, host, incoming))
  528. sys.stdout.write("D: dinstall -n %s\n" % filename)
  529. if dputhelper.check_call(command) != dputhelper.EXIT_STATUS_SUCCESS:
  530. sys.stdout.write(
  531. "Error occured while trying to connect, or while"
  532. " attempting to run dinstall.\n")
  533. sys.exit(1)
  534. def version_check(path, changes, debug):
  535. """ Check if the caller has installed the package also on his system.
  536. This is for testing purposes before uploading it. If not, we
  537. reject the upload.
  538. """
  539. files_to_check = []
  540. # Get arch
  541. dpkg_proc = subprocess.Popen(
  542. 'dpkg --print-architecture',
  543. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  544. shell=True, close_fds=True)
  545. dpkg_stdout = dputhelper.make_text_stream(dpkg_proc.stdout)
  546. dpkg_stderr = dputhelper.make_text_stream(dpkg_proc.stderr)
  547. dpkg_output = dpkg_stdout.read()
  548. dpkg_architecture = dpkg_output.strip()
  549. dpkg_stdout.close()
  550. dpkg_stderr_output = dpkg_stderr.read()
  551. dpkg_stderr.close()
  552. if debug and dpkg_stderr_output:
  553. sys.stdout.write(
  554. "D: dpkg-architecture stderr output:"
  555. " %r\n" % dpkg_stderr_output)
  556. if debug:
  557. sys.stdout.write(
  558. "D: detected architecture: '%s'\n" % dpkg_architecture)
  559. # Get filenames of deb files:
  560. for file in changes['files'].strip().split('\n'):
  561. filename = os.path.join(path, file.split()[4])
  562. if filename.endswith('.deb'):
  563. if debug:
  564. sys.stdout.write("D: Debian Package: %s\n" % filename)
  565. dpkg_proc = subprocess.Popen(
  566. 'dpkg --field %s' % filename,
  567. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  568. shell=True, close_fds=True)
  569. dpkg_stdout = dputhelper.make_text_stream(dpkg_proc.stdout)
  570. dpkg_stderr = dputhelper.make_text_stream(dpkg_proc.stderr)
  571. dpkg_output = dpkg_stdout.read()
  572. dpkg_stdout.close()
  573. dpkg_fields = email.parser.HeaderParser().parsestr(dpkg_output)
  574. dpkg_stderr_output = dpkg_stderr.read()
  575. dpkg_stderr.close()
  576. if debug and dpkg_stderr_output:
  577. sys.stdout.write(
  578. "D: dpkg stderr output:"
  579. " %r\n" % dpkg_stderr_output)
  580. if (
  581. dpkg_architecture
  582. and dpkg_fields['architecture'] not in [
  583. 'all', dpkg_architecture]):
  584. if debug:
  585. sys.stdout.write(
  586. "D: not install-checking %s due to arch mismatch\n"
  587. % filename)
  588. else:
  589. package_name = dpkg_fields['package']
  590. version_number = dpkg_fields['version']
  591. if debug:
  592. sys.stdout.write(
  593. "D: Package to Check: %s\n" % package_name)
  594. if debug:
  595. sys.stdout.write(
  596. "D: Version to Check: %s\n" % version_number)
  597. files_to_check.append((package_name, version_number))
  598. for file, version_to_check in files_to_check:
  599. if debug:
  600. sys.stdout.write("D: Name of Package: %s\n" % file)
  601. dpkg_proc = subprocess.Popen(
  602. 'dpkg -s %s' % file,
  603. stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  604. shell=True, close_fds=True)
  605. dpkg_stdout = dputhelper.make_text_stream(dpkg_proc.stdout)
  606. dpkg_stderr = dputhelper.make_text_stream(dpkg_proc.stderr)
  607. dpkg_output = dpkg_stdout.read()
  608. dpkg_stdout.close()
  609. dpkg_fields = email.parser.HeaderParser().parsestr(dpkg_output)
  610. dpkg_stderr_output = dpkg_stderr.read()
  611. dpkg_stderr.close()
  612. if debug and dpkg_stderr_output:
  613. sys.stdout.write(
  614. "D: dpkg stderr output:"
  615. " %r\n" % dpkg_stderr_output)
  616. if 'version' in dpkg_fields:
  617. installed_version = dpkg_fields['version']
  618. if debug:
  619. sys.stdout.write(
  620. "D: Installed-Version: %s\n" % installed_version)
  621. if debug:
  622. sys.stdout.write(
  623. "D: Check-Version: %s\n" % version_to_check)
  624. if installed_version != version_to_check:
  625. sys.stdout.write(
  626. "Package to upload is not installed, but it appears"
  627. " you have an older version installed.\n")
  628. else:
  629. sys.stdout.write(
  630. "Uninstalled Package. Test it before uploading it.\n")
  631. sys.exit(1)
  632. def execute_command(command, position, debug=False):
  633. """ Run a command that the user-defined in the config_file.
  634. :param command: Command line to execute.
  635. :param position: Position of the command: 'pre' or 'post'.
  636. :param debug: If true, enable debugging output.
  637. :return: ``None``.
  638. """
  639. if debug:
  640. sys.stdout.write("D: Command: %s\n" % command)
  641. if subprocess.call(command, shell=True):
  642. raise dputhelper.DputUploadFatalException(
  643. "Error: %s upload command failed." % position)
  644. def check_upload_logfile(
  645. changes_file, host, fqdn,
  646. check_only, call_lintian, force_upload, debug):
  647. """ Check if the user already put this package on the specified host.
  648. :param changes_file: Filesystem path of upload control file.
  649. :param host: Configuration host name.
  650. :param fqdn: Fully-qualified domain name of the remote host.
  651. :param check_only: If true, no upload is requested.
  652. :param call_lintian: If true, a Lintian invocation is requested.
  653. :param force_upload: If true, don't check the upload log file.
  654. :param debug: If true, enable debugging output.
  655. :return: ``None``.
  656. """
  657. uploaded = 0
  658. upload_logfile = changes_file[:-8] + '.' + host + '.upload'
  659. if not check_only and not force_upload:
  660. if not os.path.exists(upload_logfile):
  661. return
  662. try:
  663. fd_logfile = open(upload_logfile)
  664. except IOError:
  665. sys.stdout.write("Couldn't open %s\n" % upload_logfile)
  666. sys.exit(1)
  667. for line in fd_logfile.readlines():
  668. if line.find(fqdn) != -1:
  669. uploaded = 1
  670. if uploaded:
  671. sys.stdout.write(
  672. "Package has already been uploaded to %s on %s\n"
  673. % (host, fqdn))
  674. sys.stdout.write("Nothing more to do for %s\n" % changes_file)
  675. sys.exit(0)
  676. def make_usage_message():
  677. """ Make the program usage help message. """
  678. text = textwrap.dedent("""\
  679. Usage: dput [options] [host] <package(s).changes>
  680. Supported options (see man page for long forms):
  681. -c: Config file to parse.
  682. -d: Enable debug messages.
  683. -D: Run dinstall after upload.
  684. -e: Upload to a delayed queue. Takes an argument from 0 to 15.
  685. -f: Force an upload.
  686. -h: Display this help message.
  687. -H: Display a list of hosts from the config file.
  688. -l: Run lintian before upload.
  689. -U: Do not write a .upload file after uploading.
  690. -o: Only check the package.
  691. -p: Print the configuration.
  692. -P: Use passive mode for ftp uploads.
  693. -s: Simulate the upload only.
  694. -u: Don't check GnuPG signature.
  695. -v: Display version information.
  696. -V: Check the package version and then upload it.
  697. """)
  698. return text
  699. def main():
  700. """ Main function, no further comment needed. :) """
  701. global debug
  702. check_version = config_print = force_upload = 0
  703. call_lintian = no_upload_log = config_host_list = 0
  704. ftp_passive_mode = 0
  705. preferred_host = ''
  706. config_file = ''
  707. dinstall = False
  708. check_only = False
  709. unsigned_upload = False
  710. delay_upload = None
  711. simulate = False
  712. progname = dputhelper.get_progname()
  713. version = dputhelper.get_distribution_version()
  714. # Parse Command Line Options.
  715. (opts, args) = dputhelper.getopt(
  716. sys.argv[1:],
  717. 'c:dDe:fhHlUopPsuvV', [
  718. 'debug', 'dinstall', 'check-only',
  719. 'check-version', 'config=', 'force', 'help',
  720. 'host-list', 'lintian', 'no-upload-log',
  721. 'passive', 'print', 'simulate', 'unchecked',
  722. 'delayed=', 'version'])
  723. for option, arg in opts:
  724. if option in ('-h', '--help'):
  725. sys.stdout.write(make_usage_message())
  726. return
  727. elif option in ('-v', '--version'):
  728. sys.stdout.write("{progname} {version}\n".format(
  729. progname=progname, version=version))
  730. return
  731. elif option in ('-d', '--debug'):
  732. debug = 1
  733. elif option in ('-D', '--dinstall'):
  734. dinstall = True
  735. elif option in ('-c', '--config'):
  736. config_file = arg
  737. elif option in ('-f', '--force'):
  738. force_upload = 1
  739. elif option in ('-H', '--host-list'):
  740. config_host_list = 1
  741. elif option in ('-l', '--lintian'):
  742. call_lintian = 1
  743. elif option in ('-U', '--no-upload-log'):
  744. no_upload_log = 1
  745. elif option in ('-o', '--check-only'):
  746. check_only = True
  747. elif option in ('-p', '--print'):
  748. config_print = 1
  749. elif option in ('-P', '--passive'):
  750. ftp_passive_mode = 1
  751. elif option in ('-s', '--simulate'):
  752. simulate = True
  753. elif option in ('-u', '--unchecked'):
  754. unsigned_upload = True
  755. elif option in ('-e', '--delayed'):
  756. if arg in map(str, range(16)):
  757. delay_upload = arg
  758. else:
  759. sys.stdout.write(
  760. "Incorrect delayed argument,"
  761. " dput only understands 0 to 15.\n")
  762. sys.exit(1)
  763. elif option in ('-V', '--check_version'):
  764. check_version = 1
  765. # Always print the version number in the debug output
  766. # so that in case of bugreports, we know which version
  767. # the user has installed
  768. if debug:
  769. sys.stdout.write(
  770. "D: {progname} {version}\n".format(
  771. progname=progname, version=version))
  772. # Try to get the login from the enviroment
  773. if 'USER' in os.environ:
  774. login = os.environ['USER']
  775. if debug:
  776. sys.stdout.write("D: Login: %s\n" % login)
  777. else:
  778. sys.stdout.write("$USER not set, will use login information.\n")
  779. # Else use the current username
  780. login = pwd.getpwuid(os.getuid())[0]
  781. if debug:
  782. sys.stdout.write("D: User-ID: %s\n" % os.getuid())
  783. sys.stdout.write("D: Login: %s\n" % login)
  784. # Start Config File Parsing.
  785. config = read_configs(config_file, debug)
  786. if config_print:
  787. print_config(config, debug)
  788. sys.exit(0)
  789. if config_host_list:
  790. sys.stdout.write(
  791. "\n"
  792. "Default Method: %s\n"
  793. "\n" % config.get('DEFAULT', 'method'))
  794. for section in config.sections():
  795. distributions = ""
  796. if config.get(section, 'distributions'):
  797. distributions = (
  798. ", distributions: %s" %
  799. config.get(section, 'distributions'))
  800. sys.stdout.write(
  801. "%s => %s (Upload method: %s%s)\n" % (
  802. section,
  803. config.get(section, 'fqdn'),
  804. config.get(section, 'method'),
  805. distributions))
  806. sys.stdout.write("\n")
  807. sys.exit(0)
  808. # Process further command line options.
  809. if len(args) == 0:
  810. sys.stdout.write(
  811. "No package or host has been provided, see dput -h\n")
  812. sys.exit(0)
  813. elif len(args) == 1 and not check_only:
  814. package_to_upload = args[0:]
  815. else:
  816. if not check_only:
  817. if debug:
  818. sys.stdout.write(
  819. "D: Checking if a host was named"
  820. " on the command line.\n")
  821. if config.has_section(args[0]):
  822. if debug:
  823. sys.stdout.write("D: Host %s found in config\n" % args[0])
  824. # Host was also named, so only the rest will be a list
  825. # of packages to upload.
  826. preferred_host = args[0]
  827. package_to_upload = args[1:]
  828. elif (
  829. not config.has_section(args[0])
  830. and not args[0].endswith('.changes')):
  831. sys.stderr.write("No host %s found in config\n" % args[0])
  832. if args[0] == 'gluck_delayed':
  833. sys.stderr.write("""
  834. The delayed upload queue has been moved back to
  835. ftp-master (aka ftp.upload.debian.org).
  836. """)
  837. sys.exit(1)
  838. else:
  839. if debug:
  840. sys.stdout.write("D: No host named on command line.\n")
  841. # Only packages have been named on the command line.
  842. preferred_host = ''
  843. package_to_upload = args[0:]
  844. else:
  845. if debug:
  846. sys.stdout.write("D: Checking for the package name.\n")
  847. if config.has_section(args[0]):
  848. sys.stdout.write("D: Host %s found in config.\n" % args[0])
  849. preferred_host = args[0]
  850. package_to_upload = args[1:]
  851. elif not config.has_section(args[0]):
  852. sys.stdout.write("D: No host %s found in config\n" % args[0])
  853. package_to_upload = args[0:]
  854. upload_methods = import_upload_functions()
  855. # Run the same checks for all packages that have been given on
  856. # the command line
  857. for package_name in package_to_upload:
  858. # Check that a .changes file was given on the command line
  859. # and no matching .upload file exists.
  860. if package_name[-8:] != '.changes':
  861. sys.stdout.write(
  862. "Not a .changes file.\n"
  863. "Please select a .changes file to upload.\n")
  864. sys.stdout.write("Tried to upload: %s\n" % package_name)
  865. sys.exit(1)
  866. # Construct the package name for further usage.
  867. path, name_of_package = os.path.split(package_name)
  868. if path == '':
  869. path = os.getcwd()
  870. # Define the host to upload to.
  871. if preferred_host == '':
  872. host = guess_upload_host(path, name_of_package, config)
  873. else:
  874. host = preferred_host
  875. if config.get(host, 'method') == 'local':
  876. fqdn = 'localhost'
  877. else:
  878. fqdn = config.get(host, 'fqdn')
  879. # Check if we already did this upload or not
  880. check_upload_logfile(
  881. package_name, host, fqdn,
  882. check_only, call_lintian, force_upload, debug)
  883. # Run the change file tests.
  884. files_to_upload = verify_files(
  885. path, name_of_package, host,
  886. config, check_only, check_version, unsigned_upload, debug)
  887. # Run the lintian test if the user asked us to do so.
  888. if (
  889. call_lintian or
  890. config.getboolean(host, 'run_lintian') == 1):
  891. run_lintian_test(os.path.join(path, name_of_package))
  892. elif check_only:
  893. sys.stdout.write(
  894. "Warning: The option -o does not automatically include \n"
  895. "a lintian run any more. Please use the option -ol if \n"
  896. "you want to include running lintian in your checking.\n")
  897. # don't upload, skip to the next item
  898. if check_only:
  899. sys.stdout.write("Package checked by dput.\n")
  900. continue
  901. # Pre-Upload Commands
  902. if len(config.get(host, 'pre_upload_command')) != 0:
  903. position = 'pre'
  904. command = config.get(host, 'pre_upload_command')
  905. execute_command(command, position, debug)
  906. # Check the upload methods that we have as default and per host
  907. if debug:
  908. sys.stdout.write(
  909. "D: Default Method: %s\n"
  910. % config.get('DEFAULT', 'method'))
  911. if config.get('DEFAULT', 'method') not in upload_methods:
  912. sys.stdout.write(
  913. "Unknown upload method: %s\n"
  914. % config.get('DEFAULT', 'method'))
  915. sys.exit(1)
  916. if debug:
  917. sys.stdout.write(
  918. "D: Host Method: %s\n" % config.get(host, 'method'))
  919. if config.get(host, 'method') not in upload_methods:
  920. sys.stdout.write(
  921. "Unknown upload method: %s\n"
  922. % config.get(host, 'method'))
  923. sys.exit(1)
  924. # Inspect the Config and set appropriate upload method
  925. if not config.get(host, 'method'):
  926. method = config.get('DEFAULT', 'method')
  927. else:
  928. method = config.get(host, 'method')
  929. # Check now the login and redefine it if needed
  930. if (
  931. len(config.get(host, 'login')) != 0 and
  932. config.get(host, 'login') != 'username'):
  933. login = config.get(host, 'login')
  934. if debug:
  935. sys.stdout.write(
  936. "D: Login %s from section %s used\n" % (login, host))
  937. elif (
  938. len(config.get('DEFAULT', 'login')) != 0 and
  939. config.get('DEFAULT', 'login') != 'username'):
  940. login = config.get('DEFAULT', 'login')
  941. if debug:
  942. sys.stdout.write("D: Default login %s used\n" % login)
  943. else:
  944. if debug:
  945. sys.stdout.write(
  946. "D: Neither host %s nor default login used. Using %s\n"
  947. % (host, login))
  948. incoming = config.get(host, 'incoming')
  949. # if delay_upload wasn't passed via -e/--delayed
  950. if delay_upload is None:
  951. delay_upload = config.get(host, 'delayed')
  952. if not delay_upload:
  953. delay_upload = config.get('DEFAULT', 'delayed')
  954. if delay_upload:
  955. if int(delay_upload) == 0:
  956. sys.stdout.write("Uploading to DELAYED/0-day.\n")
  957. if incoming[-1] == '/':
  958. first_char = ''
  959. else:
  960. first_char = '/'
  961. incoming += first_char + 'DELAYED/' + delay_upload + '-day'
  962. delayed = ' [DELAYED/' + delay_upload + ']'
  963. else:
  964. delayed = ''
  965. # Do the actual upload
  966. if not simulate:
  967. sys.stdout.write(
  968. "Uploading to %s%s (via %s to %s):\n"
  969. % (host, delayed, method, fqdn))
  970. if debug:
  971. sys.stdout.write("D: FQDN: %s\n" % fqdn)
  972. sys.stdout.write("D: Login: %s\n" % login)
  973. sys.stdout.write("D: Incoming: %s\n" % incoming)
  974. progress = config.getint(host, 'progress_indicator')
  975. if not os.isatty(1):
  976. progress = 0
  977. if method == 'ftp':
  978. if ':' in fqdn:
  979. fqdn, port = fqdn.rsplit(":", 1)
  980. else:
  981. port = 21
  982. ftp_mode = config.getboolean(host, 'passive_ftp')
  983. if ftp_passive_mode == 1:
  984. ftp_mode = 1
  985. if debug:
  986. sys.stdout.write("D: FTP port: %s\n" % port)
  987. if ftp_mode == 1:
  988. sys.stdout.write("D: Using passive ftp\n")
  989. else:
  990. sys.stdout.write("D: Using active ftp\n")
  991. upload_methods[method](
  992. fqdn, login, incoming,
  993. files_to_upload, debug, ftp_mode,
  994. progress=progress, port=port)
  995. elif method == 'scp':
  996. if debug and config.getboolean(host, 'scp_compress'):
  997. sys.stdout.write("D: Setting compression for scp\n")
  998. scp_compress = config.getboolean(host, 'scp_compress')
  999. ssh_config_options = [
  1000. y for y in (
  1001. x.strip() for x in
  1002. config.get(host, 'ssh_config_options').split('\n'))
  1003. if y]
  1004. if debug:
  1005. sys.stdout.write(
  1006. "D: ssh config options:"
  1007. "\n "
  1008. + "\n ".join(ssh_config_options)
  1009. + "\n")
  1010. upload_methods[method](
  1011. fqdn, login, incoming,
  1012. files_to_upload, debug, scp_compress,
  1013. ssh_config_options)
  1014. else:
  1015. upload_methods[method](
  1016. fqdn, login, incoming,
  1017. files_to_upload, debug, 0, progress=progress)
  1018. # Or just simulate it.
  1019. else:
  1020. for file in files_to_upload:
  1021. sys.stdout.write(
  1022. "Uploading with %s: %s to %s:%s\n"
  1023. % (method, file, fqdn, incoming))
  1024. # Create the logfile after the package has
  1025. # been put into the archive.
  1026. if not simulate:
  1027. if not no_upload_log:
  1028. create_upload_file(
  1029. name_of_package, host, fqdn, path,
  1030. files_to_upload, debug)
  1031. sys.stdout.write("Successfully uploaded packages.\n")
  1032. else:
  1033. sys.stdout.write("Simulated upload.\n")
  1034. # Run dinstall if the user asked us to do so.
  1035. if debug:
  1036. sys.stdout.write("D: dinstall: %s\n" % dinstall)
  1037. sys.stdout.write(
  1038. "D: Host Config: %s\n"
  1039. % config.getboolean(host, 'run_dinstall'))
  1040. if config.getboolean(host, 'run_dinstall') == 1 or dinstall:
  1041. if not simulate:
  1042. dinstall_caller(
  1043. name_of_package, host, fqdn, login, incoming, debug)
  1044. else:
  1045. sys.stdout.write("Will run dinstall now.\n")
  1046. # Post-Upload Command
  1047. if len(config.get(host, 'post_upload_command')) != 0:
  1048. position = 'post'
  1049. command = config.get(host, 'post_upload_command')
  1050. execute_command(command, position, debug)
  1051. return
  1052. if __name__ == '__main__':
  1053. try:
  1054. main()
  1055. except KeyboardInterrupt:
  1056. sys.stdout.write("Exiting due to user interrupt.\n")
  1057. sys.exit(1)
  1058. except dputhelper.DputException as e:
  1059. sys.stderr.write("%s\n" % e)
  1060. sys.exit(1)
  1061. # Copyright © 2015–2016 Ben Finney <bignose@debian.org>
  1062. # Copyright © 2008–2013 Y Giridhar Appaji Nag <appaji@debian.org>
  1063. # Copyright © 2006–2008 Thomas Viehmann <tv@beamnet.de>
  1064. # Copyright © 2000–2005 Christian Kurz <shorty@debian.org>
  1065. #
  1066. # This program is free software; you can redistribute it and/or modify
  1067. # it under the terms of the GNU General Public License as published by
  1068. # the Free Software Foundation; either version 2 of the License, or
  1069. # (at your option) any later version.
  1070. #
  1071. # This program is distributed in the hope that it will be useful,
  1072. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  1073. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  1074. # GNU General Public License for more details.
  1075. #
  1076. # You should have received a copy of the GNU General Public License along
  1077. # with this program; if not, write to the Free Software Foundation, Inc.,
  1078. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  1079. # Local variables:
  1080. # coding: utf-8
  1081. # mode: python
  1082. # End:
  1083. # vim: fileencoding=utf-8 filetype=python :