async def import_pool(self, job, data): """ Import a pool. Errors: ENOENT - Pool not found """ pool = None for p in await self.middleware.call('zfs.pool.find_import'): if p['guid'] == data['guid']: pool = p break if pool is None: raise CallError(f'Pool with guid "{data["guid"]}" not found', errno.ENOENT) if data['devices']: job.check_pipe("input") args = [job.pipes.input.r, data['passphrase'], data['devices']] else: args = [] await self.middleware.call('notifier.volume_import', data.get('name') or pool['name'], data['guid'], *args) return True
async def import_pool(self, job, data): """ Import a pool. Errors: ENOENT - Pool not found """ pool = None for p in await self.middleware.call('zfs.pool.find_import'): if p['guid'] == data['guid']: pool = p break if pool is None: raise CallError(f'Pool with guid "{data["guid"]}" not found', errno.ENOENT) if data['devices']: job.check_pipe("input") args = [job.pipes.input.r, data['passphrase'], data['devices']] else: args = [] await self.middleware.call('notifier.volume_import', data.get('name') or pool['name'], data['guid'], *args) return True
def send_raw(self, job, message, config): config = dict(self.middleware.call_sync('mail.config'), **config) if config['fromname']: from_addr = Header(config['fromname'], 'utf-8') try: config['fromemail'].encode('ascii') except UnicodeEncodeError: from_addr.append(f'<{config["fromemail"]}>', 'utf-8') else: from_addr.append(f'<{config["fromemail"]}>', 'ascii') else: try: config['fromemail'].encode('ascii') except UnicodeEncodeError: from_addr = Header(config['fromemail'], 'utf-8') else: from_addr = Header(config['fromemail'], 'ascii') interval = message.get('interval') if interval is None: interval = timedelta() else: interval = timedelta(seconds=interval) sw_name = self.middleware.call_sync('system.version').split('-', 1)[0] channel = message.get('channel') if not channel: channel = sw_name.lower() if interval > timedelta(): channelfile = '/tmp/.msg.%s' % (channel) last_update = datetime.now() - interval try: last_update = datetime.fromtimestamp( os.stat(channelfile).st_mtime) except OSError: pass timediff = datetime.now() - last_update if (timediff >= interval) or (timediff < timedelta()): # Make sure mtime is modified # We could use os.utime but this is simpler! with open(channelfile, 'w') as f: f.write('!') else: raise CallError( 'This message was already sent in the given interval') verrors = self.__password_verify(config['pass'], 'mail-config.pass') if verrors: raise verrors to = message.get('to') if not to: to = [ self.middleware.call_sync('user.query', [('username', '=', 'root')], {'get': True})['email'] ] if not to[0]: raise CallError('Email address for root is not configured') if message.get('attachments'): job.check_pipe("input") def read_json(): f = job.pipes.input.r data = b'' i = 0 while True: read = f.read(1048576) # 1MiB if read == b'': break data += read i += 1 if i > 50: raise ValueError( 'Attachments bigger than 50MB not allowed yet') if data == b'': return None return json.loads(data) attachments = read_json() else: attachments = None if 'html' in message or attachments: msg = MIMEMultipart() msg.preamble = 'This is a multi-part message in MIME format.' if 'html' in message: msg2 = MIMEMultipart('alternative') msg2.attach( MIMEText(message['text'], 'plain', _charset='utf-8')) msg2.attach(MIMEText(message['html'], 'html', _charset='utf-8')) msg.attach(msg2) if attachments: for attachment in attachments: m = Message() m.set_payload(attachment['content']) for header in attachment.get('headers'): m.add_header(header['name'], header['value'], **(header.get('params') or {})) msg.attach(m) else: msg = MIMEText(message['text'], _charset='utf-8') msg['Subject'] = message['subject'] msg['From'] = from_addr msg['To'] = ', '.join(to) if message.get('cc'): msg['Cc'] = ', '.join(message.get('cc')) msg['Date'] = formatdate() local_hostname = self.middleware.call_sync('system.hostname') msg['Message-ID'] = "<%s-%s.%s@%s>" % ( sw_name.lower(), datetime.utcnow().strftime("%Y%m%d.%H%M%S.%f"), base64.urlsafe_b64encode(os.urandom(3)), local_hostname) extra_headers = message.get('extra_headers') or {} for key, val in list(extra_headers.items()): # We already have "Content-Type: multipart/mixed" and setting "Content-Type: text/plain" like some scripts # do will break python e-mail module. if key.lower() == "content-type": continue if key in msg: msg.replace_header(key, val) else: msg[key] = val syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL) try: if config['oauth']: self.middleware.call_sync('mail.gmail_send', msg, config) else: server = self._get_smtp_server(config, message['timeout'], local_hostname=local_hostname) # NOTE: Don't do this. # # If smtplib.SMTP* tells you to run connect() first, it's because the # mailserver it tried connecting to via the outgoing server argument # was unreachable and it tried to connect to 'localhost' and barfed. # This is because FreeNAS doesn't run a full MTA. # else: # server.connect() headers = '\n'.join([f'{k}: {v}' for k, v in msg._headers]) syslog.syslog(f"sending mail to {', '.join(to)}\n{headers}") server.sendmail(from_addr.encode(), to, msg.as_string()) server.quit() except Exception as e: # Don't spam syslog with these messages. They should only end up in the # test-email pane. # We are only interested in ValueError, not subclasses. if e.__class__ is ValueError: raise CallError(str(e)) syslog.syslog(f'Failed to send email to {", ".join(to)}: {str(e)}') if isinstance(e, smtplib.SMTPAuthenticationError): raise CallError( f'Authentication error ({e.smtp_code}): {e.smtp_error}', errno.EAUTH if osc.IS_FREEBSD else errno.EPERM) self.logger.warn('Failed to send email: %s', str(e), exc_info=True) if message['queue']: with MailQueue() as mq: mq.append(msg) raise CallError(f'Failed to send email: {e}') return True
async def unlock(self, job, oid, options): """ Unlock encrypted pool `id`. `passphrase` is required of a recovery key is not provided. If `recoverykey` is true this method expects the recovery key file to be uploaded using the /_upload/ endpoint. `services_restart` is a list of services to be restarted when the pool gets unlocked. Said list be be retrieve using `pool.unlock_services_restart_choices`. .. examples(websocket):: Unlock pool of id 1, restarting "cifs" service. :::javascript { "id": "6841f242-840a-11e6-a437-00e04d680384", "msg": "method", "method": "pool.unlock, "params": [1, { "passphrase": "mysecretpassphrase", "services_restart": ["cifs"] }] } """ pool = await self.middleware.call('pool.get_instance', oid) verrors = ValidationErrors() if pool['encrypt'] == 0: verrors.add('id', 'Pool is not encrypted.') elif pool['status'] != 'OFFLINE': verrors.add('id', 'Pool already unlocked.') if options.get('passphrase') and options['recoverykey']: verrors.add( 'options.passphrase', 'Either provide a passphrase or a recovery key, not both.') elif not options.get('passphrase') and not options['recoverykey']: verrors.add('options.passphrase', 'Provide a passphrase or a recovery key.') if verrors: raise verrors if options['recoverykey']: job.check_pipe("input") with tempfile.NamedTemporaryFile(mode='wb+', dir='/tmp/') as f: os.chmod(f.name, 0o600) await self.middleware.run_in_thread(shutil.copyfileobj, job.pipes.input.r, f) await self.middleware.run_in_thread(f.flush) failed = await self.middleware.call('disk.geli_attach', pool, None, f.name) else: failed = await self.middleware.call('disk.geli_attach', pool, options['passphrase']) # We need to try to import the pool even if some disks failed to attach try: await self.middleware.call('zfs.pool.import_pool', pool['guid'], { 'altroot': '/mnt', 'cachefile': ZPOOL_CACHE_FILE, }) except Exception as e: # mounting filesystems may fail if we have readonly datasets as parent if not isinstance(e, ZFSException) or e.code.name != 'MOUNTFAILED': detach_failed = await self.middleware.call( 'disk.geli_detach', pool) if failed > 0: msg = f'Pool could not be imported: {failed} devices failed to decrypt.' if detach_failed > 0: msg += ( f' {detach_failed} devices failed to detach and were left decrypted.' ) raise CallError(msg) elif detach_failed > 0: self.logger.warn('Pool %s failed to import', pool['name'], exc_info=True) raise CallError( f'Pool could not be imported ({detach_failed} devices left decrypted): {str(e)}' ) raise e await self.middleware.call('pool.sync_encrypted', oid) await self.middleware.call( 'core.bulk', 'service.restart', [[i] for i in set(options['services_restart']) | {'system_datasets', 'disk'} - {'jails', 'vms'}]) if 'jails' in options['services_restart']: await self.middleware.call('core.bulk', 'jail.rc_action', [['RESTART']]) if 'vms' in options['services_restart']: for vm in await self._unlock_restarted_vms(pool['name']): await self.middleware.call('vm.stop', vm['id']) await self.middleware.call('vm.start', vm['id']) await self.middleware.call_hook( 'pool.post_unlock', pool=pool, passphrase=options.get('passphrase'), ) return True
def send_raw(self, job, message, config=None): interval = message.get('interval') if interval is None: interval = timedelta() else: interval = timedelta(seconds=interval) sw_name = self.middleware.call_sync('system.info')['version'].split('-', 1)[0] channel = message.get('channel') if not channel: channel = sw_name.lower() if interval > timedelta(): channelfile = '/tmp/.msg.%s' % (channel) last_update = datetime.now() - interval try: last_update = datetime.fromtimestamp(os.stat(channelfile).st_mtime) except OSError: pass timediff = datetime.now() - last_update if (timediff >= interval) or (timediff < timedelta()): # Make sure mtime is modified # We could use os.utime but this is simpler! with open(channelfile, 'w') as f: f.write('!') else: raise CallError('This message was already sent in the given interval') if not config: config = self.middleware.call_sync('mail.config') verrors = self.__password_verify(config['pass'], 'mail-config.pass') if verrors: raise verrors to = message.get('to') if not to: to = [ self.middleware.call_sync( 'user.query', [('username', '=', 'root')], {'get': True} )['email'] ] if not to[0]: raise CallError('Email address for root is not configured') if message.get('attachments'): job.check_pipe("input") def read_json(): f = job.pipes.input.r data = b'' i = 0 while True: read = f.read(1048576) # 1MiB if read == b'': break data += read i += 1 if i > 50: raise ValueError('Attachments bigger than 50MB not allowed yet') if data == b'': return None return json.loads(data) attachments = read_json() else: attachments = None if 'html' in message or attachments: msg = MIMEMultipart() msg.preamble = message['text'] if 'html' in message: msg2 = MIMEMultipart('alternative') msg2.attach(MIMEText(message['text'], 'plain', _charset='utf-8')) msg2.attach(MIMEText(message['html'], 'html', _charset='utf-8')) msg.attach(msg2) if attachments: for attachment in attachments: m = Message() m.set_payload(attachment['content']) for header in attachment.get('headers'): m.add_header(header['name'], header['value'], **(header.get('params') or {})) msg.attach(m) else: msg = MIMEText(message['text'], _charset='utf-8') msg['Subject'] = message['subject'] msg['From'] = config['fromemail'] msg['To'] = ', '.join(to) if message.get('cc'): msg['Cc'] = ', '.join(message.get('cc')) msg['Date'] = formatdate() local_hostname = socket.gethostname() msg['Message-ID'] = "<%s-%s.%s@%s>" % (sw_name.lower(), datetime.utcnow().strftime("%Y%m%d.%H%M%S.%f"), base64.urlsafe_b64encode(os.urandom(3)), local_hostname) extra_headers = message.get('extra_headers') or {} for key, val in list(extra_headers.items()): if key in msg: msg.replace_header(key, val) else: msg[key] = val syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL) try: server = self._get_smtp_server(config, message['timeout'], local_hostname=local_hostname) # NOTE: Don't do this. # # If smtplib.SMTP* tells you to run connect() first, it's because the # mailserver it tried connecting to via the outgoing server argument # was unreachable and it tried to connect to 'localhost' and barfed. # This is because FreeNAS doesn't run a full MTA. # else: # server.connect() headers = '\n'.join([f'{k}: {v}' for k, v in msg._headers]) syslog.syslog(f"sending mail to {', '.join(to)}\n{headers}") server.sendmail(config['fromemail'], to, msg.as_string()) server.quit() except Exception as e: # Don't spam syslog with these messages. They should only end up in the # test-email pane. # We are only interested in ValueError, not subclasses. if e.__class__ is ValueError: raise CallError(str(e)) syslog.syslog(f'Failed to send email to {", ".join(to)}: {str(e)}') if isinstance(e, smtplib.SMTPAuthenticationError): raise CallError(f'Authentication error ({e.smtp_code}): {e.smtp_error}', errno.EAUTH) self.logger.warn('Failed to send email: %s', str(e), exc_info=True) if message['queue']: with MailQueue() as mq: mq.append(msg) raise CallError(f'Failed to send email: {e}') return True
def send(self, job, message, config=None): """ Sends mail using configured mail settings. If `attachments` is true, a list compromised of the following dict is required via HTTP upload: - headers(list) - name(str) - value(str) - params(dict) - content (str) [ { "headers": [ { "name": "Content-Transfer-Encoding", "value": "base64" }, { "name": "Content-Type", "value": "application/octet-stream", "params": { "name": "test.txt" } } ], "content": "dGVzdAo=" } ] """ interval = message.get('interval') if interval is None: interval = timedelta() else: interval = timedelta(seconds=interval) sw_name = self.middleware.call_sync('system.info')['version'].split( '-', 1)[0] channel = message.get('channel') if not channel: channel = sw_name.lower() if interval > timedelta(): channelfile = '/tmp/.msg.%s' % (channel) last_update = datetime.now() - interval try: last_update = datetime.fromtimestamp( os.stat(channelfile).st_mtime) except OSError: pass timediff = datetime.now() - last_update if (timediff >= interval) or (timediff < timedelta()): # Make sure mtime is modified # We could use os.utime but this is simpler! with open(channelfile, 'w') as f: f.write('!') else: raise CallError( 'This message was already sent in the given interval') if not config: config = self.middleware.call_sync('mail.config') to = message.get('to') if not to: to = [ self.middleware.call_sync('user.query', [('username', '=', 'root')], {'get': True})['email'] ] if not to[0]: raise CallError('Email address for root is not configured') if message.get('attachments'): job.check_pipe("input") def read_json(): f = job.pipes.input.r data = b'' i = 0 while True: read = f.read(1048576) # 1MiB if read == b'': break data += read i += 1 if i > 50: raise ValueError( 'Attachments bigger than 50MB not allowed yet') if data == b'': return None return json.loads(data) attachments = read_json() else: attachments = None if attachments: msg = MIMEMultipart() msg.preamble = message['text'] for attachment in attachments: m = Message() m.set_payload(attachment['content']) for header in attachment.get('headers'): m.add_header(header['name'], header['value'], **(header.get('params') or {})) msg.attach(m) else: msg = MIMEText(message['text'], _charset='utf-8') subject = message.get('subject') if subject: msg['Subject'] = subject msg['From'] = config['fromemail'] msg['To'] = ', '.join(to) if message.get('cc'): msg['Cc'] = ', '.join(message.get('cc')) msg['Date'] = formatdate() local_hostname = socket.gethostname() msg['Message-ID'] = "<%s-%s.%s@%s>" % ( sw_name.lower(), datetime.utcnow().strftime("%Y%m%d.%H%M%S.%f"), base64.urlsafe_b64encode(os.urandom(3)), local_hostname) extra_headers = message.get('extra_headers') or {} for key, val in list(extra_headers.items()): if key in msg: msg.replace_header(key, val) else: msg[key] = val syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_MAIL) try: server = self._get_smtp_server(config, message['timeout'], local_hostname=local_hostname) # NOTE: Don't do this. # # If smtplib.SMTP* tells you to run connect() first, it's because the # mailserver it tried connecting to via the outgoing server argument # was unreachable and it tried to connect to 'localhost' and barfed. # This is because FreeNAS doesn't run a full MTA. # else: # server.connect() headers = '\n'.join([f'{k}: {v}' for k, v in msg._headers]) syslog.syslog(f"sending mail to {', '.join(to)}\n{headers}") server.sendmail(config['fromemail'], to, msg.as_string()) server.quit() except ValueError as ve: # Don't spam syslog with these messages. They should only end up in the # test-email pane. raise CallError(str(ve)) except Exception as e: syslog.syslog(f'Failed to send email to {", ".join(to)}: {str(e)}') if isinstance(e, smtplib.SMTPAuthenticationError): raise CallError( f'Authentication error ({e.smtp_code}): {e.smtp_error}', errno.EAUTH) self.logger.warn('Failed to send email: %s', str(e), exc_info=True) if message['queue']: with MailQueue() as mq: mq.append(msg) raise CallError(f'Failed to send email: {e}') return True