Apr 15

为SSH添加两步验证 不指定

felix021 @ 2014-4-15 21:42 [IT » 网络] 评论(2) , 引用(0) , 阅读(13463) | Via 本站原创
今天从@Zavior同学那里听说了Google的开源项目Google Authenticator,它除了为Google帐号的两步验证功能提供了对应的app(安卓/iOS/黑莓)之外,还将这个功能开放出来、配套一个PAM模块,使得将两步验证功能代入ssh变得非常简单,回想起以前在B公司实习时用的那个硬件token,那真是又贵又麻烦。

步骤非常简单:

注意:如果是远程操作,请先开启一个应急连接,万一失败了还有救……

0. 安装对应的app,详见项目主页

1. 编译安装(@ubuntu)

$ sudo apt-get install libpam0g-dev libqrencode3 libtool
$ git clone https://github.com/google/google-authenticator.git
$ cd google-authenticator/libpam
$ ./bootstrap.sh && ./configure
$ make && sudo make install

[update@2016-01-04] 不知道从哪个版本开始,在ubuntu下,make install不会把pam模块拷贝到指定地点,需要手工操作:
$ sudo cp .libs/pam_google_authenticator.so /lib/security/

2. 配置openssh

$ sudo vi /etc/pam.d/sshd
  #最上方加一行 "auth required pam_google_authenticator.so"
  #这个配置可以更复杂一些,加上一些参数,详见 libpam/README
  #注:如果遇到仍然需要输入密码的情况,改成 "auth sufficient pam_google_authenticator.so" 试试。

$ sudo vi /etc/ssh/sshd_config
  #将 ChallengeResponseAuthentication 选项的 no 改成 yes
$ sudo /etc/init.d/ssh restart

3. 生成密钥

$ google-authenticator    #注:运行这个命令的是需要登录的用户,不是root用户
Do you want authentication tokens to be time-based (y/n) y  (确认:基于时间的认证token)
【这里会显示生成二维码的地址、二维码、密钥明文、应急码】
Do you want me to update your "/var/www/.google_authenticator" file (y/n) y (确认:更新配置文件)
......
size of 1:30min to about 4min. Do you want to do so (y/n) n (token有效期是1.5min,选y就是4min)
......
Do you want to enable rate-limiting (y/n) y (30s内只允许尝试三次)

4. 在app里扫二维码,或者手动输入密钥,即可看到token每隔30s更新一次了

5. 尝试登录
$ ssh localhost
verification code: 【输入验证码】
password: 【输入密码】


最后,提醒一下使用SecureCRT的同学,你需要在Session Options -> Connection -> SSH2,将Authentication中只选用 "Keyboard Interactive" ,否则没法正常登录。
Aug 16

mixo:又一个翻墙socks5代理 不指定

felix021 @ 2013-8-16 17:23 [IT » 网络] 评论(0) , 引用(0) , 阅读(14885) | Via 本站原创
因为不满ssh tunnel的使用效果,所以2012年12月某天(大概是17号)心血来潮写了这个小东西,由于 [socks5协议]( http://www.openssh.com/txt/rfc1928.txt ) 本身很简单、加上gevent/greenlet使得异步开发跟同步似的,所以200行就搞定了。但是性能上问题很大——主要是加密有问题。尽管加密就是最简单的xor,但是因为python不适合处理大量的小对象,所以当时写了一个python扩展,性能上就没问题了,但是又多了一项麻烦的依赖。后来发现已经有更成熟的shadowsocks,于是就弃坑了,也一直没有发布。

今天[2013.08.16]心血来潮,用ctypes来实现同样的功能,似乎也挺合适的;不过跟shadowsocks比起来有两个地方做得不好,一是没有更“高级”的加密方式(他家用了M2Crypto,代码看起来很复杂),另一个是shadowsocks在本地先回应socks5请求,只把必要的host:port信息发送给server,减少了一个来回,而我原先的实现则是在server端实现完整的socks5(现在把step1搬到client了,因为改动很小)。

总之好歹也是个凑合能用的东西了,发布出来晾着吧,也许哪天有人就用上了呢。

项目地址: https://github.com/felix021/mixo
Jul 11
在模板里引入其他模板应该是很常见的一种需求,但是webpy默认的template居然没有提供这种机制,挺神奇的。

官方的解决办法是把某个模板的输出给另一个模板,看起来和用起来都超级不爽;知乎网友给出的解决方案也很不爽:用render初始化时指定的layout,但是这个跟include差很多,不灵活。

实际上,由于render渲染模板后的输出本身是一个字符串,所以如果能在模板里头直接调用render渲染其他模板就最好了:而且这是可以实现的,只是略带tricky。

由于webpy给render的默认globals是空的,所以模板里只能用基本的python语法,默认连builtin的东西都用不了(比如zip、str),但是可以通过初始化render时指定globals的方式来引入:
render = web.template.render('view/', cache=False, globals = __builtins__.__dict__)


所以我们只需要把render自己也加到这个globals里头去,就可以在模板里引用它了:
render_globals = {}
render = web.template.render('view/', cache=False, globals = render_globals)
render_globals['render'] = render


第三句看起来虽然tricky,但是由于python的对象传的是引用,所以可以达到预期的效果。

这样只要在模板里这样写就行了:
$:render.header('首页') #那个冒号别漏掉:)
Feb 4

探探gethostbyname 不指定

felix021 @ 2013-2-4 23:18 [IT » 网络] 评论(0) , 引用(0) , 阅读(5032) | Via 本站原创
开篇先说一下,在它的manpage里面,有这么一句话:
引用
The gethostbyname*() and gethostbyaddr*() functions are obsolete. Applications should use getaddrinfo(3) and getnameinfo(3) instead.
这是因为gethostbyname只能处理ipv4请求(经测试ipv6.google.com不能解析,ipv6.tsinghua.edu.cn只能解析出ipv4地址),因此推荐使用 getaddrinfo 来替代它。

忽略上述问题不管的话, gethostbyname 还是有点意思的。它的原型和用法大概可以这样:
struct hostent *gethostbyname(const char *name);

struct hostent *he = gethostbyname(argv[1]);
printf("%s\n", inet_ntoa(*(struct in_addr *)he->h_addr));

用法特别简单吧,如果不是在提到这个函数的时候附带说明“它使用了内部分配的空间,因此不是线程安全的”,估计会大受欢迎的。
//注:由于windows版用了 thread local 的变量,所以在windows下它是线程安全的(只要不是同一个线程马上再调用覆盖它的话)。

相应的,像ctime、localtime之类的posix接口一样,它的线程安全版就是带了 _r 后缀的 gethostname_r ,虽然名字只复杂了一点点,但是接口可不只是复杂了一点点啊:
int gethostbyname_r(const char *name, struct hostent *ret,
    char *buf, size_t buflen, struct hostent **result, int *h_errnop);
其中的name是hostname(或者ip地址),ret应当指向一个分配好的struct hostent结构体(函数把内容填进去),buf应当指向一块分配好的空间(不小于buflen),result是一个struct hostent的二级指针(如果失败会存个NULL),h_errnop则指向某个int(失败则填对应的herr,这个奇葩函数的errno跟errno.h里面那个还不是一套)。

用起来真是相当地恶心,比如下面这个函数通过它来解析ip地址,跟前面的代码的复杂度完全不是一个量级的:
int host2addr(const char *host, struct in_addr *addr)
{
    struct hostent he, *result;
    int herr, ret, bufsz = 512;
    char *buff = NULL;
    do {
        char *new_buff = (char *)realloc(buff, bufsz);
        if (new_buff == NULL) {
            free(buff);
            return ENOMEM;
        }
        buff = new_buff;
        ret = gethostbyname_r(host, &he, buff, bufsz, &result, &herr);
        bufsz *= 2;
    } while (ret == ERANGE);

    if (ret == 0 && result != NULL)
        *addr = *(struct in_addr *)he.h_addr;
    else if (result == NULL)
        ret = herr;
    free(buff);
    return ret;
}


代码是写出来了,但是觉得不太对头,仔细看了一下 struct hostent 的定义,里头还有多个指针(h_name、h_aliases、h_addr_list)什么的:
struct hostent {
  char  *h_name;            /* official name of host */
  char **h_aliases;        /* alias list */
  int    h_addrtype;        /* host address type */
  int    h_length;          /* length of address */
  char **h_addr_list;      /* list of addresses */
}
#define h_addr h_addr_list[0] /* for backward compatibility */
如果不释放的话,不会造成内存泄漏么?

于是去看了下gethostbyname和gethostbyname_r的源码,进入 eglibc-2.15 的源码, ctags -r 然后 vim -t gethostbyname ,竟然没有这个函数。然后翻了翻,看到有个 inet 目录,里面有个 gethstbynm.c 这样一个奇葩的文件,里面除了include一些头文件之外,只有一些简单的宏定义和一个奇怪的include:
#define LOOKUP_TYPE struct hostent
#define FUNCTION_NAME  gethostbyname
#define DATABASE_NAME  hosts
#define ADD_PARAMS  const char *name
#define ADD_VARIABLES  name
#define BUFLEN      1024
#define NEED_H_ERRNO    1

#define HANDLE_DIGITS_DOTS  1

#include <nss/getXXbyYY.c>

打开一看 getXXbyYY.c 这个文件更奇葩了,根本就是用宏来实现C++中的模板功能啊。。。grep一下还发现,这个文件被18个其他文件include,也就是说,有18个函数(诸如gethostbyaddr, getnetbyaddr, 甚至getpwuid、getpwnam)都是用的这个模板。

凑合着看了一下,代码倒是不复杂(具体就不贴出来了),基本逻辑是这样的:
LOOKUP_TYPE *
FUNCTION_NAME (ADD_PARAMS)
{
    获取锁
    如buffer==NULL,分配初始化的空间
    如定义了HANDLE_DIGITS_DOTS(如gethostbyname)则检查name是否是ip地址并处理(是的话就不用发dns请求了)
    类似的while循环:调用对应的线程安全版函数,并检查是否buffer大小不够(如果是的话realloc)
    释放锁
    如果定义了NEED_H_ERRNO,设置对应的值
    返回
}

也就是说,代码流程基本上跟前面调用 gethostbyname_r 的代码一样,只是多了一个获取/释放锁的操作。根据里面的流程可以看出,代码里面不是使用静态的空间,而是动态malloc的(所以要获取锁,否则在libc内部崩溃,那不好)。由此,所谓的不是线程安全的,其实并不是函数本身,而是返回的数据。

但是,看懂了这个代码,并没有解决上面的问题啊。。。于是到 stackoverflow 上面去提了个问,很快就得到大牛的回答了:
引用
You should print out the values of the pointers in that struct to find out the answer to your question. You'll discover that they all point to data inside the buffer you allocated.

So a single free is all you need to free up all the memory.

But this also means that you must not free that allocation until you've finished using or copying whatever data you're interested in.

也就是说,我完全忽略了塞给 gethostbyname_r 的 buf 这个参数:hostent 里面的指针指向的数据就存在 buf 里面,因此最后只要 free 掉它就够了。

由于有些域名可能绑定了好多个ip(比如google.com可能就绑了数十个),buf 的空间可能不够大,所以才需要一个realloc的机制。根据实测,buf的大小从512或者1024开始比较合适,普通的域名就不需要realloc了。

到此差不多该结束了,最后吐槽一句,这两个接口真奇葩啊。。。
Oct 23

Don't Track Me Google 不指定

felix021 @ 2012-10-23 19:16 [IT » 网络] 评论(4) , 引用(0) , 阅读(6887) | Via 本站原创
用Google的第一大烦恼是随时被墙。

番羽土啬吧。

用Google的第二大烦恼是,点击的链接总要过一道Google的统计。比如搜索test,第一条是www.test.com,但是点击的时候,打开的页面是

http://www.google.com.hk/url?sa=t&rct=j&q=&esrc=s&frm=1&source=web&cd=1&ved=0CCoQFjAA&url=http%3A%2F%2Fwww.test.com%2F&ei=LHuGUMSUDKf-iAeg7YCAAg&usg=AFQjCNH21KLjC0CBkjon2DwD_CZ0HApLMw

经常发生的事情是,一直卡在这个链接上,不管是否番羽土啬,反正总是跳不过去;而大部分情况下,目标网页不需要番羽土啬也是可以顺利打开的。

至于Google呢,他才不管你是不是总卡死在这个链接,反正他们要的是你这次点击的数据。

这个问题,可以借助一个叫"Don't Track Me Google"的grease monkey脚本来实现:

http://userscripts.org/scripts/show/121923

对于Firefox,需要安装Grease Monkey插件,而Chrome用户则方便了,直接可以作为插件使用。

页面右上方有个不起眼的 Install 按钮,点一下,会下载一个 xxxx.user.js ,旧版本的chrome就直接当插件安装上去了,新版本的会提示你,不能这么安装非官方来源的插件。解决方法是:打开扩展管理页面,把这个 js 文件拖进去,会问你是否要安装,点击“添加”,done。
Sep 26

12306刷存在感@chrome 不指定

felix021 @ 2012-9-26 10:25 [IT » 网络] 评论(1) , 引用(0) , 阅读(5562) | Via 本站原创
尽管 https://dynamic.12306.cn/otsweb 的 jsessionid 有效期是一年,但是如果不几分钟发个请求的话,照样会被T出来。

所以写了这么个小代码,可以每隔一段时间自动“点”一下重新查询按钮。

=== update @ 16:40 ===
其实简单一点,按下F12,在console里面执行这个就行了...

var f=document.getElementById('main'); var btn=f.contentDocument.getElementById('submitQuery'); setInterval("btn.click()", 30000);



=== OLD ===

所以写了这么个小代码,可以每隔一段时间自动“点”一下重新查询按钮。

<div>
<textarea id="code">
var x=setInterval("document.getElementById('submitQuery').click();", 30000);alert(x);
</textarea>
<input type="button" value="run" onclick="javascript:try{eval(document.getElementById('code').value);}catch(e){alert(e);}"/>
</div>


用法:在chrome下打开订票页面,按F12,在框架内找个地方把这段代码扔进去,确定,然后在页面上点击多出来Run按钮,这样就会被个30s自动“点击”一次 [重新查询] 按钮。


Sep 3
#!/bin/bash

API=https://dnsapi.cn/Record.Ddns

IP_FILE=/tmp/dnspod_ip

function get_old_ip()
{
    ip=
    if [ -e "$IP_FILE" ]; then
        ip=`cat $IP_FILE`
    fi
    echo $ip
}

function save_ip()
{
    echo -n $1 > $IP_FILE
}

function get_new_ip()
{
    echo `nc ns1.dnspod.net 6666`
}

email=帐号邮箱
password=帐号密码  #dnspod就不能搞成个secret_key么!明文密码让人很不舒服啊。

domain_id=XXXXX #使用Domain.List API获取
record_id=YYYYY #使用Record.List API获取
sub_domain="zzz" #DDNS的二级域名

new_ip=`get_new_ip`
old_ip=`get_old_ip`

if [ "$new_ip" != "$old_ip" ];
then
    curl $API -d "format=json&login_email=$email&login_password=$password&domain_id=$domain_id&record_id=$record_id&sub_domain=$sub_domain&record_line=默认"
    save_ip $new_ip
fi

然后加入crontab,每隔15分钟跑一次进行更新

*/15 * * * * ~/bin/dnspod.sh
Jul 26
如果到Google去搜索,"How to find out which process is listening upon a port"这是第一篇文章。

事实上大部分文章都是告诉你,要么 lsof -i :80 要么 netstat -antulp | grep :80 就能找到httpd。

可是如果就这样的话,这篇BLOG就变成微博了。

事实上我的目的是希望通过编程找出这个PID,而不是调用某个命令。

第一个尝试是去看lsof的源码。找源码容易,apt-get source lsof 就行。但是源码跟大部分linux软件包一样,看起来相当晦涩。

第二个尝试是Google,但是能找到的都是命令版的。

第三个尝试是stackoverflow,没有直接搜到问题,于是准备自己提问,但是在“Questions that may already have your answer”里头找到了一篇“How to get the pid of a process that is listening on a certain port programmatically?” ( http://stackoverflow.com/questions/10996242 )。

Answer给出的步骤非常清晰:与netstat的实现一样,先读取 /proc/net/tcp 这个文件,第二个字段(local_address)冒号后面的是端口号(十六进制),第四个字段st为0A表示TCP_LISTEN,第10个字段是十进制的inode编号;而通过遍历 /proc/PIDs/fd 下面的链接,找到链接到形如 socket:[端口号] 的fd进行对比,就能知道哪些进程与该端口有一腿。

我在机器上监听的是8888端口,换成hex是22B8,但是在 /proc/net/tcp 中却找不到。幸而那篇文章的第二个Answer给了个提示,于是 strace /usr/sbin/lsof -i :8888 ,发现它还打开了 /proc/net/tcp6 (也就是对应IPv6的那个文件了)。过去一查,果然有,再顺着inode,对照lsof的结果一看,的确符合。

于是再去grep一把 lsof 的源码目录,发现在 00FAQ 文件中的 10.2.2 一节就说明了 lsof 的实现机制:
引用
Lsof identifies protocols by matching the node number associated
    with the /proc//fd entry to the node numbers found in
    selected files of the /proc/net sub-directory.  Currently
    /proc-based lsof examines these protocol files:
        /proc/net/ax25      (untested)
        /proc/net/ipx      (needs kernel patch)
        /proc/net/raw        /proc/net/raw6
        /proc/net/tcp        /proc/net/tcp6
        /proc/net/udp        /proc/net/udp6
        /proc/net/unix

看来在 Linux 下的实现方式确实只有这一种。顺便提一下,Stackoverflow上面的另一篇 http://stackoverflow.com/questions/4041003/c-what-process-is-listening-on-a-certain-port-in-windows 提到了,在Windows下可以用GetExtendedTcpTable/GetExtendedUdpTable来实现。

最后附上PHP实现的源码(这个代码用C/C++写确实蛋疼)
<?php

$filelist = array("/proc/net/tcp6", "/proc/net/tcp"); //udp/unix的就先不管了

$port2inode = array();

foreach ($filelist as $file)
{
    $lines = file($file);
    array_shift($lines);
    foreach ($lines as $line)
    {
        $values = split(" +", $line);
        list($addr, $port) = explode(":", $values[2]);
        $port = hexdec($port);
        $port2inode[$port] = $values[10];
    }
}

if ($argc < 2)
    die("usage: php {$argv[0]} PORT\n");
$findport = $argv[1];

if(!isset($port2inode[$findport]))
    die("$findport not listened\n");

$procdir = scandir("/proc");
natcasesort($procdir);

foreach ($procdir as $pid)
{
    $path = "/proc/$pid/fd";
    if (!is_readable($path) || !is_numeric($pid) || !is_dir($path)) continue;
    $dir = scandir($path);
    foreach ($dir as $file)
    {
        $link = "$path/$file";
        if (!is_link($link)) continue;
        $real = readlink($link);
        if (substr($real, 0, 7) == "socket:")
        {
            $port = substr($real, 8, -1);
            if ($port == $port2inode[$findport])
            {
                echo $pid, ": ", readlink("/proc/$pid/exe") . "\n";
                continue;
            }
        }
    }
}
?>
分页: 2/26 第一页 上页 1 2 3 4 5 6 7 8 9 10 下页 最后页 [ 显示模式: 摘要 | 列表 ]