воскресенье, 12 декабря 2010 г.

Как отключить (авто)монтирование флешек в Linux

Несколько новых способов работающих на новых дистрибутивах:

Способ № 1.
Для отключения:
chmod 0700 /media

Для включения:
chmod 0755 /media

Суть этого способа в том, что бы запретить пользователю читать данные с флешки, cd/dvd-диска и т.д. Большинство современных дистрибутивов с включённым автомонтирование монтируют все сменные устройства в директорию /media, поэтому запретив доступ к ней, запрещаем доступ ко всему ее содержимому.
Этот способ удобен, если нужно на одном и том же компьютере кому-то разрешить, а кому-то запретить доступ к внешним устройсвам хранения. В примере единственный пользователь, который может писать и чатать данные с флешки это root (если владелец и группа каталога /media - root).


Если сменить группу каталогу /media например на "storage" и сменить права на /media:
chmod 0750 /media
Только пользователи из группы "storage" смогут получить доступ к сменным носителям. (По поводу групп это все в теории так на практике не проверял)


P.S. Само устройство будет появляться и монтироватся, но с данными на нем работать будет нельзя.
P.S. CD/DVD диски пишутся НЕ копирование файлов в папку, поэтому запретить запись на них так не получится.


Способ № 2.
Для отключения:
rmmod usb_storage; echo 'blacklist usb_storage' > /etc/modprobe.d/blacklist-storage.conf

Для включения:
rm /etc/modprobe.d/blacklist-storage.conf; modprobe usb_storage

Здесь просто выгружается драйвер флешки и выключается из автоподгрузки при вставке устройства. В этом случае ни один пользователь за компьютером не сможет воспользоваться флешкой, но и сообщений о готовности устройства, как в первом способе, появляться не будет.

P.S. Если правильно помню, /etc/modprobe.d есть не во всех дистрибутивах, так что возможно потребуются незначительные изменения.

Все команды нужно выполнять от root. Тестировалось на Ubuntu 10.10.


ВНИМАНИЕ! все ниже описанное не работает с новым udev, он запрещает пустое имя устройства. Оставлено, так как может содержать полезную информацию.


От 13.12.08.

Нужно всего лишь выполнить команду:

Для отключения:
echo 'ATTRS{removable}=="1", SUBSYSTEMS=="block", NAME=""' > /etc/udev/rules.d/99-block-storage.rules

Или(если используется sudo):
sudo sh -c 'echo "ATTRS{removable}==\"1\", SUBSYSTEMS==\"block\", NAME=\"\"" > /etc/udev/rules.d/99-block-storage.rules'


Для включения:
rm /etc/udev/rules.d/99-block-storage.rules
Или закоментировать содержимое этого файла.

Разумеется все от root.
Перезагружаться, или что-то перезапусткать не нужно.

Теперь немного о том зачем все это нужно, и, что здесь все таки происходит.
Иногда, к примеру в организациях, нужно запретить сотрудникам использовать съемные носители данных(флешки, карточки, жесткие диски и т.д.). Конечно можно просто отключить USB в BIOS, но если к компьютеру подключен принтер, сканер, мышь или еще что, то задача усложняется.

Все решается просто, благодаря udev. Если коротко, то это подсистема Linux, которая отвечает за появление новый файлов устройств. Для нас важно, что можно писать правила для этих устройств.

Так выглядит наше правило:
ATTRS{removable}=="1", SUBSYSTEMS=="block", NAME=""

ATTRS{removable}=="1"
Нужны только отключаемые устройства (что бы жесткий диск случайно не отключить).

SUBSYSTEMS=="block"
Нас интересуют устройства хранения данных. (Я пробовал другие подсистемы и драйвера, но в этом случае отдельные партиции все равно подключались).

NAME=""
Собственно действие. Не создавать файл устройства.

Минусы данного подхода:

  • Нельзя выбрать пользователей которым можно или нельзя подключать флешки. (времена, когда только пользователи, входящие в группу plugdev, могли подлючать устройства прошли вместе с pmount)

  • Приведенное правило, теоретически, может совпасть с каким-нибудь устройством, которое использует SUBSYSTEMS=="block", но при этом не является устройством хранения. В этом случае в правило нужно добавить исллючение, об этом хорошо написано здесь.


Все выше описанное протестировано на Ubuntu 8.10, но должно работать на любом современном дистрибутиве.

Если подняться на уровень HAL, то открываются очень интересные возможности, как к примеру выбор пользователей кому,что и когда можно подключать. А если сюда еще замешать SELinux и/или FUSE то остановить ограничение пользователей может только фантазия :)

воскресенье, 13 июня 2010 г.

Заполнение PDF форм

Нужно было написать не большую программку, смысл которой: Много операторов вводят данные, один проверяет введенное и печатает отчеты заданного образца. Шаблоны этих отчетов мне передали некорректно сверстанные, в формате DOC. Следовательно, что бы заполнять их данными через програму нужно их переверстать (если оставить как есть, то шаблоны разваливаютсь от малейшего изменения). На верстку ни сил, ни времени, ни желания, разумеется не было.

PDF это первое, что мне приходит на ум, когда я слышу "печать документов", Поэтому я перевел все шаблоны из DOC в PDF, при помощи замечательной кнопочки в OpenOffice. Теперь по заполнению. На базе PDF можно создавать формы, которые потом заполняются либо руками, либо программно.
Только что узнал, что OpenOffice и сам умеет создавать PDF формы, достаточно добавить поля форм на страницу и экспортировать в PDF. Но в тот момент я об этом не подумал (мне должно быть стыдно!), поэтому пошел в обход и воспользовался Scribus. Импортировал в него созданные ранее PDF, как фоновые изображения и наложил сверху слой с элементами формы, потом опять экпортировал в PDF.
Вывод из этой истории: можете попробовать создать форму прямо в OpenOffice, а если не получится, или результат не будет Вас устраивать, воспользуйтесь Scribus.

Формы готовы, теперь о том, как их все таки заполнять. Моя программа написана на базе Django, поэтому я искал какое-нибудь Python ориентированное решение. Многим известный Reportlab надежд не оправдал, так как полноценная работа с существующими PDF в open source версии отсутсвует. После затяжных поисков я начал посматривать на консольные программки, которыми можно вопользоватся через os.system. Не красивое решение, но нужно было хоть что-то. Как оказалось pdftk умеет заполнять PDF формы! Ура! однако радость была не долгой ... сервер с програмкой работает под управлением 64 битной FreeBSD, но собрать pdftk под эту платформу за разумное время мне так и не удалось, а тесты на Linux показали, что с кодировками и шрифтами тоже не все гладко.

Небольшое лирическое отсупление, о том как все таки заполняют PDF формы. Алгоритм такой:
1. Сначало создается XFDF или FDF файл содержащий имена полей и что в них должно быть (от себя добавлю, забудьте про FDF это жуткий формат, пользуйтесь XFDF, который по сути обычный XML)
Формат XFDF выглядит так:
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields>
<field name="ИМЯ_ПОЛЯ">
<value>ЗНАЧЕНИЕ_ПОЛЯ</value>
</field>
</fields>
</xfdf>

2. Потом XFDF/FDF файл и PDF с формой передаются программе, которая читает данные из первого и заносит их во второй.

После возобновления поисков был найден iTextApple отношения не иммет), который, судя по описанию, умел все что нужно и его разработка шла полным ходом (в отличии от заброшенного pdftk). Однако, у этого чудо техники есть две важные особенности:
1. Это не полноценная программа, а библиотека для работы с PDF.
2. Она написана на Java ...

В связи с тем, что на очередные поиски времени не было, решил написать свою первую программу на Java.

Вот что получилось:
Скомпилированная программа: xfdffill.jar
Исходный код: XFdfFill.java
package org.fillpdf;
import java.io.*;

import com.itextpdf.text.pdf.*;

public class XFdfFill {

public static void main(String[] args) {
if (args.length != 3){
System.out.println("usage:");
System.out.println("xfdffill <out.pdf> <form.pdf> <data.xpdf>");
return;
}

try {
PdfReader pdfreader = new PdfReader(args[1]);

PdfStamper stamp = new PdfStamper(pdfreader, new FileOutputStream(args[0]));
XfdfReader fdfreader = new XfdfReader(args[2]);
AcroFields form = stamp.getAcroFields();
form.setFields(fdfreader);
stamp.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

пользоваться так:
java -jar xfdffill.jar filledform.pdf pdfform.pdf data.xfdf

Где:
filledform.pdf - Файл в котором будет заполненая PDF форма.
pdfform.pdf - Оригинальная PDF форма.
data.xfdf - XFDF файл с данными.

Далее, после успешного заполнения формы отправляю получившийся файл оператору.

Но это еще не все. Некоторые документы должны содержать внутри себя много копий одного шаблона с несколькими измененными значениями полей. Есть шаблон его нужно заполнить N количество раз и каждый N раз меняется только одно - два поля, при этом оператору нужно вывести на печать все эти документы сразу. Заставлять оператора загружать несколько файлов - это не удобно, поэтому нужно объединить все эти почти одинаковые документы в один документ и отдать оператору один PDF.

Писать больше ничего не хотелось, поэтому попробовал программы, которые объединяют несколько PDF в один. К сожалению ни одна из них не справилась с PDF формой (В моем случае нужно было, что бы форма отставалась редактируемой для оператора). Поэтому опять взялся за IText.

Получилось следующее:
Скомпилированная программа: pdfmerge.jar
Исходный код: PdfMerge.java
package org.pdfmerge;
import java.io.*;
import java.util.*;

import com.itextpdf.text.pdf.*;

public class PdfMerge {

public static void main(String[] args) {
if (args.length < 2){
System.out.println("usage:");
System.out.println("pdfmerge <out.pdf> <in1.pdf> [ ... <inN.pdf> ]");
return;
}
try{

PdfCopyFields copy = new PdfCopyFields( new FileOutputStream(args[0]));
PdfReader reader;
for( int i = 1; i < args.length; i++){
reader = new PdfReader(args[i]);
List<Integer> pages = new ArrayList<Integer>();
int allpages = reader.getNumberOfPages() + 1;
for (int p = 0; p < allpages; p++){
pages.add((Integer)p);
}
copy.addDocument(reader, pages);
}
copy.close();
}catch (Exception e) {
e.printStackTrace();
}

}

}

Пользоваться так:
java -jar pdfmerge.jar out.pdf in1.pdf in2.pdf ...

Где:
out.pdf - Файл в котором будут все PDF.
in1.pdf in2.pdf ... inN.pdf - Файлы которые нужно объединить

Эта программа хорошо справляется с файлами, которые сгенерированы IText (в частности получеными от xfdffill). Если в форме были заполненые поле не через IText (я пробовал через Scribus) то вместо значений будут кракозябры.

Принцип работы такой, заполняю одну и ту же форму с помощью xfdffill разными данными полученные pdf файлы объединяю в один с помощью pdfmerge и то что получилось отправляю оператору.
В получившемся документе есть поля с одинаковыми именами и при редактировании одного из полей данные меняются во всех полях (в моем случае это не критично, а иногда даже полезно).

И напоследок класс на Python для генирации XFDF файлов.
Исходный код: xfdfwriter.py
# -*- coding: utf-8 -*-
from xml.sax.saxutils import escape, quoteattr

class XFDFWriter():

def __init__(self, xfdf_name=None):
'''
        xfdf_name имя файла, куда нужно
        поместить результат
        '''

self.fields = {}
self.xfdf_name = xfdf_name

def setField(self, name, value):
'''
        Устанавливаем значение поля
        
        Эту функцию можно вызывать много раз.
        после каждого вызова старое значение поля заменяется новым
        '''

self.fields[name] = value

def setFields(self, names, value):
'''
        Устанавливаем одно и то же значение многим полям.

        names должен быть типа list.
        Эту функцию можно вызывать много раз.
        после каждого вызова старые значения полей заменяются новыми
        '''

for name in names:
self.setField(name, value)

def write(self, xfdf_name=None):
'''
        Записываем результат в файл.

        Если xfdf_name не задан, то используем значение
        установленное в конструкторе.
        Указывать xfdf_name в этой функции удобно,
        если нужно создать несколько xfdf файлов, в которых
        отличаются значения нескольких полей, а в основном значения
        полей одинаковы.
        '''

data = u'<?xml version="1.0" encoding="UTF-8"?>\n'
data += u'<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">\n'
data += u' <fields>'

for field in self.fields:
data += u'\n <field name=%s><value>%s</value></field>'%(quoteattr(field), escape(self.fields[field]))

data += '\n</fields>'
data += '\n</xfdf>\n'

if not xfdf_name:
xfdf_name = self.xfdf_name

xfdf_file = open(xfdf_name, 'w')
xfdf_file.write(data.encode("utf-8"))
xfdf_file.close()


Ниже приведу пример использования этого класса.

# -*- coding: utf-8 -*-

#Подключаем XFDFWriter

from xfdfwriter import XFDFWriter

# Создаем экземпляр XFDFWriter который будет писать
# данные в 'file.xfdf'

xfdf = XFDFWriter('file.xfdf')

# Устанавливаем значения полей 'name', 'year_start', и 'year_end'

xfdf.setField('name', u'Проект №1')
xfdf.setField('year_start', u'2000 г.')
xfdf.setField('year_end', u'2003 г.')

# Записываем xfdf в файл 'file.xfdf' указанный при
# создании экземпляра этого класса

xfdf.write()

# Переопределяем значение поля 'year_start'
# (значение полей 'name' и 'year_end' остается прежним)

xfdf.setField('year_start', u'2001 г.')

# Записываем новый вариант в файл file1.xfdf'

xfdf.write('file1.xfdf')

# Переопределяем значение полей 'year_start' и 'year_end'
# (значение поля 'name' остается прежним)

xfdf.setFields( ['year_start','year_end' ], u'2002 г.')

# Записываем новый вариант в файл file2.xfdf'

xfdf.write('file2.xfdf')


P.S. Лицензия на Java программки такая же как и на IText т.е. AGPL.
Лицензия на Python программки public domain т.е. какая хотите :)

P.P.S. Использовать java через os.system очень неправильно, когда будет минутка объединю эти две программки в одного демона, который будет слушать pipe, а пока пользуюсь тем, что оператор выводящий документы только один, и в моем распоряжении мощный сервер.