"""Network diagnostics, version display, and usage functions.""" import logging import os import platform import socket import ssl import struct import sys import arrow import httplib2 from cryptography import x509 from cryptography.hazmat.backends import default_backend from importlib.metadata import version as lib_version from urllib.parse import urlparse from gamlib import glapi as API from gamlib import glcfg as GC from gamlib import glglobals as GM from gamlib import glmsgs as Msg from gamlib import glverlibs from gam.constants import ( FN_GAMCOMMANDS_TXT, GAM, GAM_URL, GAM_WIKI, GOOGLE_TIMECHECK_LOCATION, MAX_LOCAL_GOOGLE_TIME_OFFSET, NETWORK_ERROR_RC, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, ) from util.args import ISOformatTimeStamp, getArgument, getString, todaysTime from util.display import printBlankLine, printKeyValueList from util.errors import unknownArgumentExit from util.output import ( createGreenText, createRedText, createYellowText, flushStdout, stderrWarningMsg, systemErrorExit, writeStdout, ) from util.api import doGAMCheckForUpdates, getHttpObj, getService, handleServerError, waitOnFailure # gam.__init__ attributes that can't be imported at module level # (connection.py is imported BY __init__.py during init) _gam = lambda: sys.modules['gam'] # --- Constants --- def _buildTimeOffsetUnits(): return [('day', SECONDS_PER_DAY), ('hour', SECONDS_PER_HOUR), ('minute', SECONDS_PER_MINUTE), ('second', 1)] MACOS_CODENAMES = { 10: { 6: 'Snow Leopard', 7: 'Lion', 8: 'Mountain Lion', 9: 'Mavericks', 10: 'Yosemite', 11: 'El Capitan', 12: 'Sierra', 13: 'High Sierra', 14: 'Mojave', 15: 'Catalina', 16: 'Big Sur' }, 11: 'Big Sur', 12: 'Monterey', 13: 'Ventura', 14: 'Sonoma', 15: 'Sequoia', 26: 'Tahoe', } # --- Functions --- def getLocalGoogleTimeOffset(testLocation=None): if testLocation is None: testLocation = GOOGLE_TIMECHECK_LOCATION TIME_OFFSET_UNITS = _buildTimeOffsetUnits() # If local time is well off, it breaks https because the server certificate will be seen as too old or new and thus invalid; http doesn't have that issue. # Try with http first, if time is close (.googleapis.com so # add those domains. disc_hosts = [] for api, config in API._INFO.items(): if config.get('v2discovery') and not config.get('localdiscovery'): if mapped_api := config.get('mappedAPI'): api = mapped_api host = f'{api}.googleapis.com' if host not in disc_hosts: disc_hosts.append(host) for host in disc_hosts: check_host(host) checked_hosts = initial_hosts + api_hosts + disc_hosts # now we need to "build" each API and check it's base URL host # if we haven't already. This may not be any hosts at all but # to ensure we are checking all hosts GAM may use we should # keep this. for api in API._INFO: if api in [API.CONTACTS, API.EMAIL_AUDIT]: continue svc = getService(api, httpObj) base_url = svc._rootDesc.get('baseUrl') parsed_base_url = urlparse(base_url) base_host = parsed_base_url.netloc if base_host not in checked_hosts: writeStdout(f'Checking {base_host} for {api}\n') check_host(base_host) checked_hosts.append(base_host) if success_count == try_count: writeStdout(createGreenText('All hosts passed!\n')) else: systemErrorExit(3, createYellowText('Some hosts failed to connect! Please follow the recommendations for those hosts to correct any issues and try again.')) # gam comment def doComment(): writeStdout(_gam().Cmd.QuotedArgumentList(_gam().Cmd.Remaining())+'\n') # gam version [check|checkrc|simple|extended] [timeoffset] [nooffseterror] [location ] def doVersion(checkForArgs=True): Ent = _gam().Ent Cmd = _gam().Cmd forceCheck = 0 extended = noOffsetError = timeOffset = simple = False testLocation = GOOGLE_TIMECHECK_LOCATION if checkForArgs: while Cmd.ArgumentsRemaining(): myarg = getArgument() if myarg == 'check': forceCheck = 1 elif myarg == 'checkrc': forceCheck = -1 elif myarg == 'simple': simple = True elif myarg == 'extended': extended = timeOffset = True elif myarg == 'timeoffset': timeOffset = True elif myarg == 'nooffseterror': noOffsetError = True elif myarg == 'location': testLocation = getString(Cmd.OB_HOST_NAME) else: unknownArgumentExit() if simple: writeStdout(_gam().__version__) return writeStdout((f'{GAM} {_gam().__version__} - {GAM_URL} - {GM.Globals[GM.GAM_TYPE]}\n' f'{_gam().__author__}\n' f'Python {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} {struct.calcsize("P")*8}-bit {sys.version_info[3]}\n' f'{getOSPlatform()} {platform.machine()}\n' f'Path: {GM.Globals[GM.GAM_PATH]}\n' f'{Ent.Singular(Ent.CONFIG_FILE)}: {GM.Globals[GM.GAM_CFG_FILE]}, {Ent.Singular(Ent.SECTION)}: {GM.Globals[GM.GAM_CFG_SECTION_NAME]}, ' f'{GC.CUSTOMER_ID}: {GC.Values[GC.CUSTOMER_ID]}, {GC.DOMAIN}: {GC.Values[GC.DOMAIN]}\n' f'Time: {ISOformatTimeStamp(todaysTime())}\n' )) if sys.platform.startswith('win') and str(struct.calcsize('P')*8).find('32') != -1 and platform.machine().find('64') != -1: printKeyValueList([Msg.UPDATE_GAM_TO_64BIT]) if timeOffset: offsetSeconds, offsetFormatted = getLocalGoogleTimeOffset(testLocation) printKeyValueList([Msg.YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE.format(testLocation, offsetFormatted)]) if offsetSeconds > MAX_LOCAL_GOOGLE_TIME_OFFSET: if not noOffsetError: systemErrorExit(NETWORK_ERROR_RC, Msg.PLEASE_CORRECT_YOUR_SYSTEM_TIME) stderrWarningMsg(Msg.PLEASE_CORRECT_YOUR_SYSTEM_TIME) if forceCheck: doGAMCheckForUpdates(forceCheck) if extended: printKeyValueList([ssl.OPENSSL_VERSION]) tls_ver, cipher_name = _getServerTLSUsed(testLocation) for lib in glverlibs.GAM_VER_LIBS: try: writeStdout(f'{lib} {lib_version(lib)}\n') except: pass printKeyValueList([f'{testLocation} connects using {tls_ver} {cipher_name}']) # gam help def doUsage(): printBlankLine() doVersion(checkForArgs=False) writeStdout(Msg.HELP_SYNTAX.format(os.path.join(GM.Globals[GM.GAM_PATH], FN_GAMCOMMANDS_TXT))) writeStdout(Msg.HELP_WIKI.format(GAM_WIKI))