Привет, Хабр! В этой статье я хотел бы поделиться своим опытом создания демона для удаленного управления компьютером с помощью команд по электронной почте.
Введение
В своей работе я использую множество удаленных машин.Зачастую доступ к ним ограничен IP-фильтром, поэтому для входа в машину приходится использовать длинные цепочки хостов.
Пройдя этот квест еще раз, чтобы выполнить пару команд, я понял, что нужно что-то менять.
Конечно, самым простым решением было бы создать прямой SSH-туннель и забыть обо всех сложностях, но, во-первых, этому препятствует жесткая политика безопасности, а, во-вторых, хотелось бы иметь гибкую и независимую систему.
Со временем сложился ряд требований:
- безопасность системы;
- легкий доступ к системе без лишних движений (с телефона, чужого компьютера и т. д.);
- история выполненных команд и результатов выполнения.
Сегодня вы можете отправлять электронную почту с любого устройства, имеющего экран и клавиатуру (компьютер, телефон, Kindle, даже телевизоры уже могут это делать).
Список писем всегда доступен на сервере и вы можете удобно анализировать изменения состояния системы.
Среди готовых решений ничего подходящего не нашлось, поэтому я решил сделать это сам.
Выполнение
В качестве языка программирования был выбран Python; критерием выбора была не только гибкость самого языка, но и давнее желание использовать его на практике.Алгоритм программы достаточно прост:
- Получение команд по электронной почте
- Выполнение команд
- Отправка результатов обратно пользователю
1. Получение команд по электронной почте.
Сначала устанавливаем соединение с сервером, есть два варианта: POP3 или IMAP4. Выбор зависит как от поддерживаемых протоколов на почтовом сервере, так и от открытости портов на целевой машине.
Подключение к серверу по протоколу POP3
Подключение к серверу по протоколу IMAP4if is_enabled(self.get_param_str("Mail", "USE_SSL")): session = poplib.POP3_SSL(self.get_param_str("Mail", "POP_SERVER"), self.get_param_int("Mail", "POP_SSL_PORT")) else: session = poplib.POP3(self.get_param_str("Mail", "POP_SERVER"), self.get_param_int("Mail", "POP_PORT")) #if if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")): session.set_debuglevel(10) #if try: session.user(self.get_param_str("Mail", "EMAIL_USER")) session.pass_(self.get_param_str("Mail", "EMAIL_PASS")) except poplib.error_proto as e: sys.stderr.write("Got an error while connecting to POP server: '%s'\n" % (e)) return False #try
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
session = imaplib.IMAP4_SSL(self.get_param_str("Mail", "IMAP_SERVER"),
self.get_param_int("Mail", "IMAP_SSL_PORT"))
else:
session = imaplib.IMAP4(self.get_param_str("Mail", "IMAP_SERVER"),
self.get_param_int("Mail", "IMAP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
session.debug = 10
#if
try:
session.login(self.get_param_str("Mail", "EMAIL_USER"),
self.get_param_str("Mail", "EMAIL_PASS"))
except imaplib.IMAP4.error as e:
sys.stderr.write("Got an error while connecting to IMAP server: '%s'\n" % (e))
return False
#try
После того, как соединение установлено, нам необходимо отфильтровать команды для нашего бота из всех сообщений.
Я решил использовать трехуровневую фильтрацию:
- фильтрация по теме сообщения;
- фильтрация отправителей по белому и черному спискам;
- авторизация по логину+паролю.
numMessages = len(session.list()[1])
for i in range(numMessages):
m_parsed = Parser().
parsestr("\n".
join(session.top(i+1, 0)[1])) if self.get_param_str('Main', 'SUBJECT_CODE_PHRASE') == m_parsed['subject']: #Looks like valid cmd for bot, continue if self._process_msg("\n".
join(session.retr(i+1)[1])):
session.dele(i+1)
#if
#if
#for
В случае с IMAP все немного проще, протокол позволяет осуществлять выборку на стороне сервера, т.е.
нам достаточно указать тему, и сервер сам выдаст нам все подходящие буквы.
session.select(self.get_param_str('Mail', 'IMAP_MAILBOX_NAME'))
typ, data = session.search(None,
'SUBJECT', self.get_param_str("Main", "SUBJECT_CODE_PHRASE"))
Следующий шаг — фильтрация отправителя по белому и черному спискам (можно использовать регулярные выражения).
И последний бастион — авторизация с использованием пары логин:пароль, которая должна появиться в первой строке письма с командой.
На клиенте вместо паролей хранятся только md5 хеши.
Да, я понимаю, что это слишком параноидально, с другой стороны, можно ли говорить о чрезмерной паранойе в вопросах безопасности?
2. Выполнение команд
Поскольку выполнение некоторых команд потенциально могло занять значительное время, было решено выполнять каждую команду в отдельном процессе.Также был введен верхний предел количества активных процессов.
Недостатком выполнения произвольных команд является возможность зависания системы запуском интерактивной программы (mc, htop и т.п.
).
Я пока не придумал, как с этим бороться.
3. Отправка результатов обратно пользователю
После завершения пользовательской команды пользователю отправляется отчет, содержащий все выходные данные команды и код возврата.
Модуль smtplib используется для отправки self.__send_lock.acquire()
if not msg is None:
print "[%s] Sending response to '%s'" % (datetime.today().
strftime('%d/%m/%y %H:%M'), email_from) recipients = [email_from, self.get_param_str('Mail', 'SEND_COPY_TO')] message = "%s%s%s\n%s" % ('From: %s \n' % (self.get_param_str('Main', 'BOT_NAME')), 'To: %s \n' % (email_from), 'Subject: Report %s \n' % (datetime.today().
strftime('%d/%m/%y %H:%M')),
msg)
# Currently in python SMTP_SSL is broken, so always using usual version
session = smtplib.SMTP(self.get_param_str("Mail", "SMTP_SERVER"),
self.get_param_int("Mail", "SMTP_PORT"))
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
session.set_debuglevel(10)
#if
session.login(self.get_param_str("Mail", "EMAIL_USER"),
self.get_param_str("Mail", "EMAIL_PASS"))
session.sendmail(self.get_param_str("Mail", "EMAIL_USER"),
recipients,
message)
session.quit()
#if
self.__send_lock.release()
Был использован для создания демона этот Сорт.
Заключение
В качестве примера отправляем боту команду:Через некоторое время видим ответ:
Код проекта доступен на github Надеюсь, что эта информация будет кому-то полезна.
Спасибо за внимание, жду ваших комментариев.
UPD: исправлена ошибка, связанная с некорректной обработкой многочастных сообщений, благодаря пользователю github megres. UPD2: добавлена возможность устанавливать собственный таймаут для команды.
Для использования нужно перед командой добавить префикс ":time=x", т.е.
":time=10 make", даст 10 секунд на сборку, а потом выстрелит. Спасибо Таненн за идею.
Теги: #python #дистанционное управление #уведомления по электронной почте #python
-
Viacom Тайно Загрузила Видео На Youtube
19 Oct, 24 -
Живой Компьютер Из Бактерий
19 Oct, 24 -
Краеугольный Камень Мировоззрения Фрилансера
19 Oct, 24 -
10 Новых Бесплатных Онлайн-Курсов На Stepic
19 Oct, 24