FileNotFoundError文件存在时(在当前脚本中创建时)

时间:2016-10-17 17:26:35

标签: python file-not-found

我正在尝试创建一个安全(例如,SSL / HTTPS)XML-RPC客户端服务器。当我的系统上存在所需的证书时,客户端 - 服务器部分可以正常工作;但是,当我在执行期间尝试创建证书时,即使证书明确存在,我也会在打开ssl-wrapped套接字时收到 FileNotFoundError (因为创建了上一个函数)他们。)

为什么文件存在时会给出FileNotFoundError? (如果我只是关闭并重新启动python脚本,打开套接字时不会产生任何错误,一切都没有问题。)

我在其他地方寻找解决方案,但我发现的最佳/最接近的答案可能是创建证书和打开证书之间的“竞争条件”。但是,我尝试添加“睡眠”以减轻竞争条件的可能性(以及通过用户输入菜单单独运行每个功能),每次都有相同的错误。

我缺少什么?

以下是我的代码片段:

import os
import threading
import ssl 
from xmlrpc.server import SimpleXMLRPCServer
import certs.gencert as gencert  # <---- My python module for generating certs

...

rootDomain = "mydomain"
CERTFILE = "certs/mydomain.cert"
KEYFILE = "certs/mydomain.key"

...

def listenNow(ipAdd, portNum, serverCert, serverKey):

    # Create XMLRPC Server, based on ipAdd/port received
    server = SimpleXMLRPCServer((ipAdd, portNum))

    # **THIS** is what causes the FileNotFoundError ONLY if
    # the certificates are created during THE SAME execution
    # of the program.
    server.socket = ssl.wrap_socket(server.socket,
                                    certfile=serverCert,
                                    keyfile=serverKey,
                                    do_handshake_on_connect=True,
                                    server_side=True)
    ...
    # Start server listening [forever]
    server.serve_forever()

...

# Verify Certificates are present; if not present,
# create new certificates
def verifyCerts():

    # If cert or key file not present, create new certs
    if not os.path.isfile(CERTFILE) or not os.path.isfile(KEYFILE):

        # NOTE: This [genert] will create certificates matching 
        # the file names listed in CERTFILE and KEYFILE at the top
        gencert.gencert(rootDomain)
        print("Certfile(s) NOT present; new certs created.")

    else:
        print("Certfiles Verified Present")


# Start a thread to run server connection as a daemon
def startServer(hostIP, serverPort):

    # Verify certificates present prior to starting server
    verifyCerts()

    # Now, start thread
    t = threading.Thread(name="ServerDaemon",
                         target=listenNow,
                         args=(hostIP,
                               serverPort,
                               CERTFILE,
                               KEYFILE
                               )
                         )
    t.daemon = True
    t.start()


if __name__ == '__main__':
    startServer("127.0.0.1", 12345)
    time.sleep(60)  # <--To allow me to connect w/client before closing

当我运行上述操作时,如果没有证书,这就是我收到的错误:

$ python3 test.py
Certfile(s) NOT present; new certs created.
Exception in thread ServerDaemon:
Traceback (most recent call last):
File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
self.run()
File "/usr/lib/python3.5/threading.py", line 862, in run
self._target(*self._args, **self._kwargs)
File "test.py", line 41, in listenNow
server_side=True)
File "/usr/lib/python3.5/ssl.py", line 1069, in wrap_socket
ciphers=ciphers)
File "/usr/lib/python3.5/ssl.py", line 691, in __init__
self._context.load_cert_chain(certfile, keyfile)
FileNotFoundError: [Errno 2] No such file or directory

当我只是第二次重新运行脚本时(即,启动时证书文件已经存在,所有内容都按预期运行,没有错误,我可以很好地连接我的客户端。

$ python3 test.py
Certfiles Verified Present

什么阻止ssl.wrap_socket函数查看/访问刚刚创建的文件(从而产生FileNotFoundError异常)?

编辑1: 感谢John Gordon的评论。以下是gencert.py的副本,由Atul Varm提供,在此处找到https://gist.github.com/toolness/3073310

import os
import sys
import hashlib
import subprocess
import datetime

OPENSSL_CONFIG_TEMPLATE = """
prompt = no
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
C                      = US
ST                     = IL
L                      = Chicago
O                      = Toolness
OU                     = Experimental Software Authority
CN                     = %(domain)s
emailAddress           = varmaa@toolness.com
[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[ alt_names ]
DNS.1 = %(domain)s
DNS.2 = *.%(domain)s
"""

MYDIR = os.path.abspath(os.path.dirname(__file__))
OPENSSL = '/usr/bin/openssl'
KEY_SIZE = 1024
DAYS = 3650
CA_CERT = 'ca.cert'
CA_KEY = 'ca.key'

# Extra X509 args. Consider using e.g. ('-passin', 'pass:blah') if your
# CA password is 'blah'. For more information, see:
#
# http://www.openssl.org/docs/apps/openssl.html#PASS_PHRASE_ARGUMENTS
X509_EXTRA_ARGS = ()

def openssl(*args):
    cmdline = [OPENSSL] + list(args)
    subprocess.check_call(cmdline)

def gencert(domain, rootdir=MYDIR, keysize=KEY_SIZE, days=DAYS,
            ca_cert=CA_CERT, ca_key=CA_KEY):
    def dfile(ext):
        return os.path.join('domains', '%s.%s' % (domain, ext))

    os.chdir(rootdir)

    if not os.path.exists('domains'):
        os.mkdir('domains')

    if not os.path.exists(dfile('key')):
        openssl('genrsa', '-out', dfile('key'), str(keysize))

    # EDIT 3: mydomain.key gets output here during execution

    config = open(dfile('config'), 'w')
    config.write(OPENSSL_CONFIG_TEMPLATE % {'domain': domain})
    config.close()

    # EDIT 3: mydomain.config gets output here during execution

    openssl('req', '-new', '-key', dfile('key'), '-out', dfile('request'),
            '-config', dfile('config'))

    # EDIT 3: mydomain.request gets output here during execution    

    openssl('x509', '-req', '-days', str(days), '-in', dfile('request'),
            '-CA', ca_cert, '-CAkey', ca_key,
            '-set_serial',
            '0x%s' % hashlib.md5(domain + 
                                 str(datetime.datetime.now())).hexdigest(),
            '-out', dfile('cert'),
            '-extensions', 'v3_req', '-extfile', dfile('config'),
            *X509_EXTRA_ARGS)

    # EDIT 3: mydomain.cert gets output here during execution

    print "Done. The private key is at %s, the cert is at %s, and the " \
          "CA cert is at %s." % (dfile('key'), dfile('cert'), ca_cert)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print "usage: %s <domain-name>" % sys.argv[0]
        sys.exit(1)
    gencert(sys.argv[1])

编辑2: 关于John的评论,“这可能意味着正在创建这些文件,但不在目录中[I]期望”

当我在另一个窗口中打开目录时,我会在执行期间看到正确位置中的文件弹出。此外,当第二次运行test.py脚本而没有更改时,文件将被标识为存在于正确(相同)的位置。这让我相信文件位置不是问题。谢谢你的建议。我会继续寻找。

编辑3: 我逐步完成了gencert.py程序,并在执行期间的正确时间正确输出了每个文件。我在上面的文件中输出了确切的内容,标有“EDIT 3”

当gencert暂停等待我的输入(raw_input)时,我可以在其他程序中打开/查看/编辑上述文件,没有任何问题。

此外,第一个test.py实例正在运行(暂停,等待用户输入,就在mydomain.cert出现之后),我可以在另一个终端运行test.py的第二个实例,它会看到/使用这些文件正好。

然而,在第一个实例中,如果我继续该程序,则输出“FileNotFoundError”。

1 个答案:

答案 0 :(得分:0)

上述问题源于约翰建议的os.chdir(rootdir)的使用;但是,细节与创建的文件在错误的位置略有不同。问题是正在运行的程序的当前工作目录(cwd)由gencert()更改。以下是具体内容:

  1. 该程序以test.py启动,调用verifyCerts()。此时,程序正在当前运行的文件夹test.py的目录中运行。此时使用os.getcwd()查找当前目录。在这种情况下(作为示例),它正在运行:

    /home/name/testfolder/

  2. 接下来,os.path.isfile()查找文件“certs / mydomain.cert”和“certs / mydomain.key”;基于上面的文件路径[例如,cwd],它正在寻找以下文件:

    /home/name/testfolder/certs/mydomain.cert
    /home/name/testfolder/certs/mydomain.key

  3. 上述文件不存在,因此程序会执行gencert.gencert(rootDomain) 正确按照预期在上面第2号中提到的确切位置创建两个文件。

  4. 问题确实是os.chdir()调用:当gencert()执行时,它使用os.chdir()将cwd更改为“rootdir”,是os.path.abspath(os.path.dirname(__file__)),它是当前文件(gencert.py)的目录。由于此文件位于更深的文件夹中,因此新的cwd变为:

    /home/name/testfolder/certs/

  5. gencert()完成执行并且控件返回test.py时,cwd再也不会改变;即使执行返回/home/name/testfolder/certs/,cwd仍为test.py

  6. 稍后,当ssl.wrap_socket()尝试查找serverCert和serverKey时,它会在cwd 中查找“certs / mydomain.cert”和“certs / mydomain.key” ,所以这里是它正在寻找的完整路径:

    /home/name/testfolder/certs/certs/mydomain.cert /home/name/testfolder/certs/certs/mydomain.key

  7. 这两个文件 NOT 存在,因此程序正确返回“FileNotFoundError”。

  8. <强>解决方案

    A)将“gencert.py”文件移动到与“test.py”相同的目录

    B)在“gencert.py”的开头添加cwd = os.getcwd()来记录程序的原始cwd;然后,在最后,添加os.chdir(cwd)以在结束之前更改回原始cwd并将控制权交还给调用程序。

    我选择了'B',我的程序现在完美无瑕。我很感谢John Gordon的帮助,指出我找到问题的根源。