Last active
          February 2, 2024 14:52 
        
      - 
      
- 
        Save saquibtmf/ade487b76f16d61339d659f58d3b3f62 to your computer and use it in GitHub Desktop. 
    updated the SVN store pass script to pass username as an argument rather than from stdin
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | #!/usr/bin/env python3 | |
| """\ | |
| Script to store password in plaintext in ~/.subversion/auth/svn.simple/ | |
| Useful in case Subversion is compiled without support for writing | |
| passwords in plaintext. | |
| Only use this script if the security implications are understood | |
| and it is acceptable by your organization to store passwords in plaintext. | |
| See https://subversion.apache.org/faq.html#plaintext-passwords | |
| """ | |
| # ==================================================================== | |
| # Licensed to the Apache Software Foundation (ASF) under one | |
| # or more contributor license agreements. See the NOTICE file | |
| # distributed with this work for additional information | |
| # regarding copyright ownership. The ASF licenses this file | |
| # to you under the Apache License, Version 2.0 (the | |
| # "License"); you may not use this file except in compliance | |
| # with the License. You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, | |
| # software distributed under the License is distributed on an | |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| # KIND, either express or implied. See the License for the | |
| # specific language governing permissions and limitations | |
| # under the License. | |
| # ==================================================================== | |
| import os | |
| import sys | |
| TERMINATOR = b"END\n" | |
| PARSERDESCR = """\ | |
| Store plaintext password in ~/.subversion/auth/svn.simple/ | |
| Existing passwords and authentication realms can be inspected by: | |
| svn auth [--show-passwords] | |
| The authentication realm can also be found using: | |
| svn info URL | |
| """ | |
| def _read_one_datum(fd, letter): | |
| """\ | |
| Read a 'K <length>\\n<key>\\n' or 'V <length>\\n<value>\\n' block from | |
| an svn_hash_write2()-format FD. | |
| LETTER identifies the first letter, as a bytes object. | |
| """ | |
| assert letter in {b'K', b'V'} | |
| # Read the letter and the space | |
| readletter = fd.read(1) | |
| if readletter != letter or fd.read(1) != b' ': | |
| raise ValueError('Hash file format error: Expected {} got {}'.format(letter, readletter)) | |
| # Read the length and the newline | |
| line = fd.readline() | |
| if line[-1:] != b'\n': | |
| raise ValueError('Hash file format error: Expected trailing \\n') | |
| expected_length = int(line[:-1]) | |
| # Read the datum and its newline | |
| datum = fd.read(expected_length) | |
| if len(datum) != expected_length: | |
| raise ValueError('Hash file format error: Expected length {} got {}'.format(expected_length, len(datum))) | |
| if fd.read(1) != b'\n': | |
| raise ValueError('Hash file format error: Extra data after reading {} bytes, expected \\n') | |
| return datum | |
| # Our version of svn_hash_read2(), named without "svn_" prefix to avoid | |
| # potential naming conflicts with stuff star-imported from svn.core. | |
| def hash_read(fd): | |
| """\ | |
| Read an svn_hash_write2()-formatted file from FD, terminated by "END". | |
| Return a dict mapping bytes to bytes. | |
| """ | |
| assert 'b' in fd.mode | |
| assert TERMINATOR[0] not in {b'K', b'V'} | |
| ret = {} | |
| while True: | |
| if fd.peek(1)[0] == TERMINATOR[0]: | |
| if fd.readline() != TERMINATOR: | |
| raise ValueError('Hash file format error: Expected file terminator {}'.format(TERMINATOR)) | |
| if fd.peek(1): | |
| raise ValueError('Hash file format error: Extra content after file terminator') | |
| return ret | |
| key = _read_one_datum(fd, b'K') | |
| value = _read_one_datum(fd, b'V') | |
| ret[key] = value | |
| def outputHash(fd, hash): | |
| """\ | |
| Write a dictionary HASH to an open file descriptor FD in the | |
| svn_hash_write2()-format, terminated by "END\\n". | |
| The keys and values must have datatype 'bytes' and strings must be | |
| encoded using utf-8. | |
| """ | |
| assert 'b' in fd.mode | |
| for key, val in hash.items(): | |
| fd.write(b'K ' + bytes(str(len(key)), 'utf-8') + b'\n') | |
| fd.write(key + b'\n') | |
| fd.write(b'V ' + bytes(str(len(val)), 'utf-8') + b'\n') | |
| fd.write(val + b'\n') | |
| fd.write(TERMINATOR) | |
| def writeHashFile(filename, hash): | |
| """\ | |
| Write the dict HASH to a file named FILENAME in svn_hash_write2() | |
| format. | |
| """ | |
| tmpFilename = filename + '.tmp' | |
| try: | |
| with open(tmpFilename, 'xb') as fd: | |
| outputHash(fd, hash) | |
| os.rename(tmpFilename, filename) | |
| except FileExistsError: | |
| print('{}: File {!r} already exist. Is the script already running?' | |
| .format(os.path.basename(__file__), tmpFilename), | |
| file=sys.stderr) | |
| except: | |
| os.remove(tmpFilename) | |
| raise | |
| def main(): | |
| # These imports are only being used by main | |
| import argparse | |
| import getpass | |
| import hashlib | |
| # Parse arguments | |
| parser = argparse.ArgumentParser( | |
| description=PARSERDESCR, | |
| formatter_class=argparse.RawDescriptionHelpFormatter) | |
| parser.add_argument('realm', help='Server authentication realm') | |
| parser.add_argument('-u', '--user', help='Set username') | |
| parser.add_argument('-p', '--password', help='Set password') | |
| args = parser.parse_args() | |
| # The file name is the md5encoding of the realm | |
| m = hashlib.md5() | |
| m.update(args.realm.encode('utf-8')) | |
| authfileName = os.path.join(os.path.expanduser('~/.subversion/auth/svn.simple/'), m.hexdigest()) | |
| # If the authfile doesn't already exist, verify that a username has been provided | |
| # or else prompt for it | |
| existingFile = os.path.exists(authfileName) | |
| if not existingFile and args.user is None: | |
| args.user = input("Enter username for realm {}: ".format(args.realm)) | |
| if args.user == '': | |
| parser.exit(1, 'Username required.\n') | |
| # If the authfile doesn't already exist, verify that a password has been provided or Prompt for password | |
| if not existingFile and args.user is None: | |
| args.password = getpass.getpass("Enter password for realm {}: ".format(args.realm)) | |
| if args.password == '': | |
| parser.exit(1, 'Password required.\n') | |
| # In an existing file, we add/replace password/username/passtype | |
| if existingFile: | |
| hash = hash_read(open(authfileName, 'rb')) | |
| if args.user is not None: | |
| hash[b'username'] = args.user.encode('utf-8') | |
| hash[b'password'] = args.password.encode('utf-8') | |
| hash[b'passtype'] = b'simple' | |
| # For a new file, set realmstring, username, password and passtype | |
| else: | |
| hash = { | |
| b'svn:realmstring': args.realm.encode('utf-8'), | |
| b'username': args.user.encode('utf-8'), | |
| b'passtype': b'simple', | |
| b'password': args.password.encode('utf-8'), | |
| } | |
| del args.password | |
| # Write out the resulting file | |
| writeHashFile(authfileName, hash) | |
| if __name__ == '__main__': | |
| main() | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment