def drop_privileges():
    '''
     Drop root privileges and run on behalf of the specified
     unprivileged users.
    '''
    passwd = getpwnam()
    utils_posix.chuser(passwd)
Exemple #2
0
def __main():
    ''' Neubot auto-updater process '''

    # Process command line options
    logopt = syslog.LOG_PID
    daemonize = True

    try:
        options, arguments = getopt.getopt(sys.argv[1:], 'adv')
    except getopt.error:
        sys.exit('Usage: neubot/updater/unix.py [-adv]')

    if arguments:
        sys.exit('Usage: neubot/updater/unix.py [-adv]')

    check_for_updates = 0  # By default we don't check for updates
    for tpl in options:
        if tpl[0] == '-a':
            check_for_updates = 1
        elif tpl[0] == '-d':
            daemonize = False
        elif tpl[0] == '-v':
            logopt |= syslog.LOG_PERROR|syslog.LOG_NDELAY

    # We must be run as root
    if os.getuid() != 0 and os.geteuid() != 0:
        sys.exit('FATAL: You must be root.')

    # Open the system logger
    syslog.openlog('neubot(updater)', logopt, syslog.LOG_DAEMON)

    # Clear root user environment
    utils_posix.chuser(utils_posix.getpwnam('root'))

    # Daemonize
    if daemonize:
        utils_posix.daemonize('/var/run/neubot.pid')

    #
    # TODO We should install a signal handler that kills
    # properly the child process when requested to exit
    # gracefully.
    #

    firstrun = True
    pid = -1

    signal.signal(signal.SIGUSR1, __sigusr1_handler)

    #
    # Loop forever, catch and just log all exceptions.
    # Spend many time sleeping and wake up just once every
    # few seconds to make sure everything is fine.
    #
    while True:
        if firstrun:
            firstrun = False
        else:
            time.sleep(15)

        # Read configuration files
        CONFIG.update(utils_rc.parse_safe('/etc/neubot/updater'))
        CONFIG.update(utils_rc.parse_safe('/etc/neubot/users'))

        try:

            # If needed start the agent
            if pid == -1:
                syslog.syslog(syslog.LOG_INFO, 'Starting the agent')
                pid = __start_neubot_agent()

            # Check for updates
            now = time.time()
            updates_check_in = 1800 - (now - STATE['lastcheck'])
            if updates_check_in <= 0:
                STATE['lastcheck'] = now

                if check_for_updates:
                    nversion = _download_and_verify_update()
                    if nversion:
                        if pid > 0:
                            __stop_neubot_agent(pid)
                            pid = -1
                        __install_new_version(nversion)
                        __switch_to_new_version()
                        raise RuntimeError('Internal error')

                    #
                    # We have not found an update, while here make
                    # sure that we keep clean our base directory,
                    # remove old files and directories, the tarball
                    # of this version, etc.
                    #
                    else:
                        __clear_base_directory()
                else:
                    syslog.syslog(syslog.LOG_DEBUG, 'Auto-updates are disabled')

            elif check_for_updates:
                syslog.syslog(syslog.LOG_DEBUG,
                  'Auto-updates check in %d sec' % updates_check_in)

            # Monitor the agent
            rpid, status = __waitpid(pid, 0)

            if rpid == pid:
                pid = -1

                # Signaled?
                if os.WIFSIGNALED(status):
                    raise RuntimeError('Agent terminated by signal')

                # For robustness
                if not os.WIFEXITED(status):
                    raise RuntimeError('Internal error in __waitpid()')

                syslog.syslog(syslog.LOG_WARNING,
                  'Child exited with status %d' % os.WEXITSTATUS(status))

        except:
            try:
                why = asyncore.compact_traceback()
                syslog.syslog(syslog.LOG_ERR, 'In main loop: %s' % str(why))
            except:
                pass
Exemple #3
0
def __download(address, rpath, tofile=False, https=False, maxbytes=67108864):

    '''
     Fork an unprivileged child that will connect to @address and
     download from @rpath, using https: if @https is True and http:
     otherwise.  If @tofile is False the output is limited to 8192
     bytes and returned as a string.  Otherwise, if @tofile is True,
     the return value is the path to the file that contains the
     response body.
    '''

    syslog.syslog(syslog.LOG_INFO,
                  '__download: address=%s rpath=%s tofile=%d '
                  'https=%d maxbytes=%d' % (address, rpath,
                  tofile, https, maxbytes))

    # Create communication pipe
    fdin, fdout = os.pipe()
    flags = fcntl.fcntl(fdin, fcntl.F_GETFL)
    flags |= os.O_NONBLOCK
    fcntl.fcntl(fdin, fcntl.F_SETFL, flags)

    if not tofile:
        lfdesc, lpath = -1, None
    else:

        # Build output file name
        basename = os.path.basename(rpath)
        lpath = os.sep.join([BASEDIR, basename])

        #
        # If the output file exists and is a regular file
        # unlink it because it might be an old possibly failed
        # download attempt.
        #
        if os.path.exists(lpath):
            if not os.path.isfile(lpath):
                raise RuntimeError('%s: not a file' % lpath)
            os.unlink(lpath)

        # Open the output file (384 == 0600)
        lfdesc = os.open(lpath, os.O_RDWR|os.O_CREAT, 384)

    # Fork off a new process
    pid = os.fork()

    if pid > 0:

        # Close unneeded descriptors
        if lfdesc >= 0:
            os.close(lfdesc)
        os.close(fdout)

        # Wait for child process to complete
        status = __waitpid(pid)[1]

        # Read child process response
        try:
            response = os.read(fdin, 8192)
        except OSError:
            response = ''

        # Close communication pipe
        os.close(fdin)

        # Terminated by signal?
        if os.WIFSIGNALED(status):
            syslog.syslog(syslog.LOG_ERR,
                          'Child terminated by signal %d' %
                          os.WTERMSIG(status))
            return None

        # For robustness
        if not os.WIFEXITED(status):
            raise RuntimeError('Internal error in __waitpid()')

        # Failure?
        if os.WEXITSTATUS(status) != 0:
            error = __printable_only(response.replace('ERROR ', '', 1))
            syslog.syslog(syslog.LOG_ERR, 'Child error: %s' % error)
            return None

        # Is output a file?
        if tofile:
            syslog.syslog(syslog.LOG_ERR, 'Response saved to: %s' % lpath)
            return lpath

        #
        # Output inline
        # NOTE The caller is expected to validate the result
        # using regular expression.  Here we use __printable_only
        # for safety.
        #
        result = response.replace('OK ', '', 1)
        syslog.syslog(syslog.LOG_ERR, 'Response is: %s' %
                             __printable_only(result))
        return result

    else:

        #
        # The child code is surrounded by this giant try..except
        # because what is interesting for us is the child process
        # exit status (plus eventually the reason).
        #
        try:

            # Lookup unprivileged user info
            passwd = utils_posix.getpwnam(CONFIG['update_user'])

            # Become unprivileged as soon as possible
            utils_posix.chuser(passwd)

            if os.getuid() == 0 or os.geteuid() == 0:
                raise RuntimeError('Has not dropped privileges')

            # Close all unneeded file descriptors
            for tmpdesc in range(64):
                if tmpdesc == lfdesc or tmpdesc == fdout:
                    continue
                try:
                    os.close(tmpdesc)
                except OSError:
                    pass
                except:
                    pass
            # Ensure stdio point to something
            for _ in range(3):
                os.open('/dev/null', os.O_RDWR)

            # Send HTTP request
            if https:
                connection = __lib_http.HTTPSConnection(address)
            else:
                connection = __lib_http.HTTPConnection(address)
            headers = {'User-Agent': utils_version.HTTP_HEADER}
            connection.request("GET", rpath, None, headers)

            # Recv HTTP response
            response = connection.getresponse()
            if response.status != 200:
                raise RuntimeError('HTTP response: %d' % response.status)

            # Need to write response body to file?
            if tofile:

                assert(lfdesc >= 0)

                total = 0
                while True:

                    # Read a piece of response body
                    data = response.read(262144)
                    if not data:
                        break

                    # Enforce maximum response size
                    total += len(data)
                    if total > maxbytes:
                        raise RuntimeError('Response is too big')

                    # Copy to output descriptor
                    os.write(lfdesc, data)

                # Close I/O channels
                os.close(lfdesc)
                connection.close()

                # Notify parent
                os.write(fdout, 'OK\n')

            else:
                vector = []
                total = 0
                while True:
                    data = response.read(262144)
                    if not data:
                        break
                    vector.append(data)
                    total += len(data)
                    if total > 8192:
                        raise RuntimeError('Response is too big')
                connection.close()
                os.write(fdout, 'OK %s\n' % ''.join(vector))

        except:
            try:
                why = asyncore.compact_traceback()
                os.write(fdout, 'ERROR %s\n' % str(why))
            except:
                pass
            __exit(1)
        else:
            __exit(0)