简介:cyrus-sasl-2.1.21.tar.gz 是 Cyrus SASL 认证与授权库的经典源码发行包,面向自建邮件服务器、需要为 Postfix 等 MTA 开启 SMTP AUTH 的运维与后端开发人员,用于解决明文口令传输、开放中继与未授权访问等安全隐患,也是接入 PLAIN、CRAM-MD5、DIGEST-MD5 等认证机制的基础依赖,适合具备一定 Linux 编译基础、希望从源码层面掌握邮件认证流程的读者。压缩包共 620 个文件、约 1.51MB,其中 155 个 .c 与 138 个 .h 构成服务端、客户端及插件核心实现,51 个 .m4 连同 configure、Makefile.in 支撑 autotools 构建,42 个 .3 手册页与多个 README、ChangeLog 记录接口语义与版本沿革,另含 Java 绑定示例和 LDAP、Kerberos 相关样例。已有 392 人学习下载。解压后可按需用 ./configure 搭配 --with-ldap、--with-db 等参数裁剪认证后端,再结合 smtpd_sasl_path、sasl2/smtpd.conf 与邮件系统对接;包内 sasl_client_new、sasl_server_new、sasl_listmech 等手册页逐条说明接口,配合插件源码便于理清机制加载流程、定位认证失败原因,对排查机制不匹配、密码库路径错误等常见问题也有直接参考价值。
1. 为什么要自己编译 cyrus-sasl 2.1.21:一个版本号决定认证链路能不能通
前段时间处理一个邮件服务发信全 535 的问题,日志里只有一行SASL authentication failed。查了半天,密码没输错,账号也正常,最后发现是系统自带的那套 libsasl2 没有被禁用,而应用编译时链接到了另一份 cyrus-sasl 的头文件,导致插件目录和库目录不是同一个来源,PLAIN机制根本没加载起来。cyrus-sasl 2.1.21 这个包,是做 LDAP 目录服务、邮件服务、老中间件对接时经常遇到的一个版本,很多旧系统的认证模块都是按这个版本的 ABI 和目录布局编译的。装对了,认证走通只需要十分钟;装错了,就会一直卡在密码验证失败,日志里看不出原因。
这篇笔记按实际部署顺序记录:先清楚依赖,再确定目录,然后 configure、make、配置 saslauthd,最后用三层验证把链路跑通。适合手上有一个 2.1.21 源码包、需要在生产或测试机从零跑通 SASL 认证的人。
2. 装前环境盘点:cyrus-sasl 2.1.21 的依赖、编译器与目录规划
老源码包最容易带来麻烦的地方在于:它检测依赖时采取“没有对应开发包就安静跳过”的策略,configure 不会报错,make 也能通过,直到你装到应用上才发现少了某个机制。所以动手前先把环境列清楚,比后面到处翻 config.log 省事得多。
2.1 依赖清单:缺一个开发包,configure 就会悄悄关掉一个特性
cyrus-sasl 2.1.21 的本体不依赖什么重量级组件,但它的插件是分块的,每个插件对应一组可选依赖。下表是我在两类发行版上常用的对照,缺哪个就补哪个:
| 依赖 | 用在哪 | apt 系包名 | yum 系包名 |
|---|---|---|---|
| gcc / make | 全部编译过程 | build-essential | gcc make |
| OpenSSL 头文件 | DIGEST-MD5、OTP、SRP 插件 | libssl-dev | openssl-devel |
| PAM 头文件 | saslauthd 的-a pam后端 | libpam0g-dev | pam-devel |
| libtool | 生成 .so / .la | libtool | libtool |
| Berkeley DB 头文件 | sasldb 后端(可选) | libdb-dev | libdb-devel |
| krb5 头文件 | GSSAPI 插件(多数场景不需要) | libkrb5-dev | krb5-devel |
装完先做一次工具链自检,把版本和头文件路径确认掉,避免 configure 因为找不到ssl.h而静默关掉 OpenSSL:
# 确认编译器与 OpenSSL 版本,2.1.21 对 OpenSSL 1.1 以上要额外处理,后面会讲 gcc --version | head -n 1 openssl version # 确认关键头文件真的在,configure 只看这个 ls -l /usr/include/openssl/ssl.h ls -l /usr/include/security/pam_appl.h # 确认 pkg-config 能用,有些发行版拆到单独的包里 pkg-config --version这段命令的作用很明确:gcc --version用来判断编译器大版本,gcc 10 以上要留意后面提到的-fno-common问题;openssl version决定要不要在处理插件时增加参数;两个ls是在确认开发包是否真的装了,光装运行时包(libssl、libpam)是不够的,头文件在-dev/-devel包里。很多“configure 通过了但插件没编出来”的问题,病因就在这两个ls上。
提示:如果机器上已经有别的软件在用系统自带的 libsasl2,不要用
apt remove之类的方式去卸载它,下面 2.3 节会用目录隔离的方式共存。
2.2 三个目录参数:--prefix、--with-configdir、--with-plugindir 怎么定
cyrus-sasl 2.1.21 运行时需要知道两件事:认证插件.so放在哪里,以及应用级配置(比如smtpd.conf)放在哪里。这两个路径都在 configure 阶段确定并编进库里的,装完再改只能重新编译,所以先定下来。
我一般的做法是把它们统一放在一个独立前缀下:
--prefix=/usr/local/sasl2:所有产物集中在一个目录,不散落到/usr/local根上,将来卸载或回滚直接删目录。--with-configdir=/usr/local/sasl2/lib/sasl2:应用级 conf 文件的搜索目录,sasl_server_init()启动时就是来这里找<服务名>.conf。--with-plugindir=/usr/local/sasl2/lib/sasl2:插件.so的目录,saslpluginviewer打印的“插件搜索路径”就是它。--with-saslauthd=/var/run/saslauthd:saslauthd 的 mux socket 默认目录,这个值会被编进库,应用侧如果没有显式写saslauthd_path,用的就是它。
configdir 和 plugindir 设成同一个目录是 cyrus-sasl 的传统布局,也是最省事的做法:conf 和插件放一起,排查时ls一下就能看到全貌。分开也不是不行,但要求你对每个调用方的参数都足够清楚,容易出问题。
还有一点要提前确认:这个后缀为lib的目录在 64 位机器上可能是lib64(取决于具体使用的 configure 脚本),装完用find确认一下实际位置,别照抄文档里的路径。
2.3 和系统自带 libsasl2 共存:做隔离,不要做覆盖
绝大多数发行版都预装了libsasl2.so.2,系统的 PAM 模块、包管理工具自己都会用到它。你从源码装的 2.1.21 如果直接--prefix=/usr,会把系统的库覆盖掉,运气不好连sudo之外的认证流程都会受影响。常见做法是独立前缀加动态库路径配置:
# 把自编译的库目录注册进动态链接器搜索路径 echo "/usr/local/sasl2/lib" > /etc/ld.so.conf.d/sasl2-local.conf ldconfig # 确认系统能同时看到两份库 ldconfig -p | grep libsasl2/etc/ld.so.conf.d/*.conf里的内容是给ldconfig读的,ldconfig会把它刷新进/etc/ld.so.cache。ldconfig -p的输出里应该能同时看到/usr/local/sasl2/lib/libsasl2.so.2和系统那份/lib64/libsasl2.so.2,说明两份共存成功了。
但共存不等于应用会用你这份。动态链接器是按 cache 顺序找的,系统目录通常排在前面,结果就是你以为装好了,ldd一看还是链接到系统那份。验证方法在最后一章会写,这里先记住一个原则:应用编译时用--with-sasl=/usr/local/sasl2,或者显式加CPPFLAGS="-I/usr/local/sasl2/include"和LDFLAGS="-L/usr/local/sasl2/lib -Wl,-rpath,/usr/local/sasl2/lib",把 rpath 写进二进制是最稳的做法,不受ldconfig顺序影响。
3. 从 tar.gz 到 make install:2.1.21 的 configure 参数逐条过
前面的准备工作完成之后,这一段就是把源码包变成一个能运行的环境。2.1.21 是很早的版本,它的configure脚本和config.guess都是十几年前的产物,在新机器上直接运行大概率无法通过,所以第一步不是 configure,而是先修辅助脚本。
3.1 解包与 config.guess 修补:老 tarball 在新架构上的第一道关
tar -zxvf cyrus-sasl-2.1.21.tar.gz cd cyrus-sasl-2.1.21 # 先看这两个辅助脚本能不能正确识别当前架构 ./config.guess ./config.sub $(./config.guess)config.guess的作用是探测当前机器的三元组(CPU-厂商-系统),config.sub负责校验并规范化它。2.1.21 包里的这两个脚本大概停留在 2006 年前后,在 x86_64 上一般够用,但在 ARM 或一些新架构上会直接输出空行或者报cannot guess build type。
如果真的遇到无法识别的情况,有两种修补方式。一是从系统里较新的 automake 复制一份:
# 系统里一般会有较新版本的这两个脚本,直接替换掉老的 cp /usr/share/automake-*/config.guess . cp /usr/share/automake-*/config.sub . ./config.guess如果没有安装 automake,那就退一步,在 configure 时手动指定--build参数,把这个三元组显式传给 configure,跳过自动探测。这种方式不改动源码包,可复现性更好,我一般优先选它。
注意:不要顺手执行
autoreconf -fi。2.1.21 的configure.ac是给老版本 autoconf 写的,用新版本重新生成,经常出现语法或宏兼容问题,问题会比它解决的还多。
3.2 一条能跑通的 configure 命令,逐段说明
下面这条命令是我在 x86_64 上跑 2.1.21 时用的参数组合,服务器端只保留 PLAIN、LOGIN 和 sasldb 后端,把依赖 Kerberos 和 OpenSSL 老 API 的部分关掉,编译成功率最高:
./configure \ --prefix=/usr/local/sasl2 \ --with-configdir=/usr/local/sasl2/lib/sasl2 \ --with-plugindir=/usr/local/sasl2/lib/sasl2 \ --with-saslauthd=/var/run/saslauthd \ --enable-plain \ --enable-login \ --enable-auth-sasldb \ --with-pam \ --with-openssl=/usr \ --disable-otp \ --disable-gssapi \ --disable-ldapdb \ --disable-static \ CFLAGS="-O2 -fcommon"逐段解释一下取舍:
--enable-plain --enable-login是服务端机制开关,其实 2.1.21 默认就开着,但显式写出来可以避免以后有人改 configure 默认值时出现意外。--enable-auth-sasldb打开 sasldb 后端,让saslpasswd2建出来的账号能直接用于认证,这是不接入 PAM 时最轻量的方案。--with-pam让 saslauthd 支持-a pam,也就是用系统账号密码认证,这条通常是刚需。
--with-openssl=/usr指定 OpenSSL 的前缀,/usr意味着头文件在/usr/include、库在/usr/lib64。如果你的 OpenSSL 是自编译在/usr/local/ssl,这里就要跟着改,否则插件编不出来。
--disable-otp --disable-gssapi --disable-ldapdb是三个特意关掉的开关。OTP 插件用的是 OpenSSL 里早已废弃的 RSA 接口,在 1.1 以上版本编译大概率报错;GSSAPI 需要 krb5 开发包,纯账号密码认证用不上;ldapdb 后端要求机器上装了 LDAP 开发库,同样用不上。关掉它们不是省事,是减少失败点。
--disable-static只生成动态库,省掉.a文件,也省掉后面体积和路径上的麻烦。CFLAGS="-O2 -fcommon"里-fcommon是给 gcc 10 及以上版本准备的关键参数,老代码里重复定义的全局变量在新编译器下会直接导致链接失败。
configure 跑完后不要只看最后一行config.status是否生成。想看哪些特性真的被检测到了,看config.log,或者更直接一点,等 make 完看plugins/.libs/下生成了哪些.so:
# configure 结束后立刻检查关键探测项 grep -iE "checking for openssl|checking for pam|checking for berkeley|checking for kerberos" config.log这条grep的作用是把 config.log 里所有和可选依赖有关的探测结论筛出来。如果openssl那行显示的是 not found,而你的机器上明明装了,那就说明头文件路径没传对,加CPPFLAGS重新 configure,别带着这个结果继续 make。
3.3 make 与 make install:装完的目录长什么样
# 先单线程跑一遍,报错信息最清晰 make # 通过之后再并行加速重编,节省时间 make clean make -j"$(nproc)" # 安装到前面定好的前缀 make install第一次用make而不是make -j,是血泪经验:并行编译时多个子目录的报错会交错输出,老源码包又不一定能正确管理依赖,报错被截断在半行的情况很常见。等单线程跑通一次,确认没有编译错误之后,再清掉并行重编,这样能节省不少时间。
装完之后先看一遍目录结构,这一步比任何文档都直观:
# 看主目录布局 find /usr/local/sasl2 -maxdepth 2 -type d | sort # 看编出来的插件都有哪些,这个列表决定了服务端能通告什么机制 ls -1 /usr/local/sasl2/lib/sasl2/*.so # 确认 pkg-config 描述文件在,方便别的软件用 pkg-config 找我这份 ls -l /usr/local/sasl2/lib/pkgconfig/libsasl2.pc第一个find给你的是目录结构:bin/下是saslpasswd2、sasldblistusers2、testsaslauthd这些工具,sbin/下是saslauthd守护进程,lib/sasl2/下是插件和将来的 conf 文件。第二个ls是重点,如果这里只有libsasl2.so而没有libplain.so、liblogin.so、libsasldb.so这三个,说明前面的开关或依赖有问题,这时不用往下走,直接回 configure 重来。
第三个ls检查 pkg-config 文件,很多应用在编译时用pkg-config --libs libsasl2找库,这个文件不在的话,它们会报找不到 SASL,然后你又得回到“手工加 -I 和 -L”的老办法上。
注意:自定义前缀安装不会自动刷新动态库缓存,装完记得回到 2.3 节那条
ldconfig再跑一次,否则saslauthd可能起不来,报error while loading shared libraries: libsasl2.so.2。
4. 配置 saslauthd 与 mech 列表:让 PLAIN / LOGIN 真正生效
库装好了只是有实现,能不能用取决于两件事:应用级 conf 里选了什么认证方式,以及后端验证服务(saslauthd 或 sasldb)是否正常。这一段是实际排障最集中的地方。
4.1 应用级 conf:文件名必须等于服务名
cyrus-sasl 的 application config 格式很简单,就是几行key: value,关键在于文件名——服务端调用sasl_server_init(NULL, "smtpd")时,第二个参数就是文件名主体,它去找的是smtpd.conf。写成smtp.conf就会被静默忽略,然后你会在日志里看到no mechanism available。
# 服务名为 smtpd 的应用级配置,放在 configdir 下 cat > /usr/local/sasl2/lib/sasl2/smtpd.conf <<'EOF' # 明文密码交给 saslauthd 校验,不走 auxprop 直接查库 pwcheck_method: saslauthd # 只通告这两种机制,减少客户端协商时选到不支持的能力 mech_list: PLAIN LOGIN # 必须和 saslauthd 启动时的 -m 参数完全一致 saslauthd_path: /var/run/saslauthd/mux # 排错期开 5,稳定后降到 1,避免日志过大 log_level: 5 EOF四个参数的含义要说清楚。pwcheck_method决定明文密码交给谁验证,可选值有saslauthd、auxprop、sasldb、pam,其中auxprop是让库自己去查 auxprop 插件(比如 sasldb2),saslauthd是把密码发给守护进程。服务端要支持多个进程共享认证状态时,用saslauthd更合适。
mech_list是上限,不是开关:即使插件都编出来了,没写在这行里的机制也不会被通告。反过来,如果只写PLAIN但客户端只支持LOGIN,协商一样会失败。所以对接前先确认好客户端支持什么。
saslauthd_path指向的是 mux 这个 socket 文件,不是目录。写成目录会得到cannot connect to saslauthd server之类的错误,路径还看不出问题在哪。
log_level在排障时开 5,syslog 里能看到机制协商和认证结果;确认稳定后调到 1,不然高并发下日志压力不小。
同样的格式可以复制一份给目录服务用,文件名换成目录服务进程传入的服务名即可。两个应用共用一份 saslauthd,配置基本一致。
4.2 saslauthd 的启动参数与 socket 路径对齐
saslauthd 是整个认证链路的后端验证进程,它的启动参数里-a、-m、-n三个最关键:
# 建立 socket 目录,权限要给足,否则应用进程连不上 mux mkdir -p /var/run/saslauthd chmod 755 /var/run/saslauthd # 前台调试先跑一次,把问题暴露在终端上 /usr/local/sasl2/sbin/saslauthd -a pam -c -m /var/run/saslauthd -n 5 -d # 确认没问题后改成后台常驻(生产环境建议交给 systemd 管理) /usr/local/sasl2/sbin/saslauthd -a pam -c -m /var/run/saslauthd -n 5-a pam指定认证后端:用pam就走系统 PAM 栈,用shadow直接读/etc/shadow(需要 root 权限且不能和 PAM 共存),用sasldb走saslpasswd2建的库。选pam的理由是它和系统账号体系一致,不用额外维护一份密码库。
-c开凭据缓存,作用是把认证成功的凭据缓存一小段时间,避免每次请求都走一遍 PAM。代价是改密码之后旧凭据可能还能用一会儿,敏感环境建议不开。
-m /var/run/saslauthd是 socket 目录,这里必须和 4.1 节配置里saslauthd_path的父目录完全对应。-n 5是预启动的认证进程数,并发高的时候可以调到 10 到 20,ps里能看到几个saslauthd子进程。
第一次启动一定要加-d前台跑,它会把 PAM 的交互过程打印出来,比翻 syslog 快得多。确认终端里能正常看到认证成功,再切后台。
还有一个容易被漏掉的点:-a pam要求系统里存在与被认证服务同名的 PAM 服务文件。服务名是smtpd,PAM 就去找/etc/pam.d/smtpd。这个文件不存在的话,saslauthd 会一直返回认证失败,日志里只有一句很笼统的authentication failure:
# 给服务名补一个最小可用的 PAM 配置 cat > /etc/pam.d/smtpd <<'EOF' auth required pam_unix.so account required pam_unix.so EOF这两行表示认证和账号检查都交给pam_unix.so,也就是本地/etc/passwd和/etc/shadow。在部分发行版上还需要补password、session类型,但那属于应用侧行为,纯认证不需要。
4.3 用 saslpasswd2 建库:不走 PAM 的轻量方案
如果不想让服务依赖系统账号(比如虚拟账号场景),可以用 sasldb2 这一套。它的用户库是一个 Berkeley DB 文件,用saslpasswd2管理:
# 创建用户,-c 表示新建,-u 指定 realm,-a 指定应用名 /usr/local/sasl2/sbin/saslpasswd2 -c -u example.com -a smtpd alice # 列出库里所有用户,确认真的写进去了 /usr/local/sasl2/sbin/sasldblistusers2 # 改密码(不加 -c 就是修改已有用户) /usr/local/sasl2/sbin/saslpasswd2 -u example.com -a smtpd alice-a smtpd这个参数要和应用名对齐,库里的记录是按应用分组的,服务端用auxprop方式查库时,查的就是这个应用名下的记录。-u example.com是 realm,客户端不指定 realm 时服务端会用它做默认值,对接方要求带域名的场景下必须写对。
库文件的实际路径取决于 configure 时的前缀,不同 prefix 下默认位置不一样,用-h看当前这份二进制的说明最靠谱,别照抄别人文档里的路径。
权限是这个方案最容易出问题的地方。库文件默认只有创建者能读,而实际读它的是应用进程账号,所以建完库要补一步:
# 找到实际库文件位置后,把属组改成运行应用的那个账号 chgrp mailuser /usr/local/sasl2/etc/sasldb2 chmod 640 /usr/local/sasl2/etc/sasldb2640的意思是属主可读写、属组可读、其他不可见,够用且不会把密码库暴露出去。改完顺手确认一下应用进程能不能读到,不然认证时会返回一个非常含糊的generic failure,从日志上完全看不出是权限问题。
用 sasldb 方案时,4.1 节的 conf 要改成:
pwcheck_method: auxprop auxprop_plugin: sasldb mech_list: PLAIN LOGIN把pwcheck_method从saslauthd换成auxprop并指定插件名,这条路不经过 saslauthd 进程,少一个组件也就少一个故障点,代价是库文件权限和并发写入要自己管好。
5. 常见问题排查:cyrus-sasl 2.1.21 最容易翻车的五个地方
下面五条是这套版本组合下反复出现的,按“现象、原因、解决”记录,省得每次重新推导。
5.1 configure 报 cannot guess build type
现象:./configure直接退出,最后一行是configure: error: cannot guess build type; you must specify one,前面的探测项都是checking build system type...之后断掉。
原因:包里的config.guess太老,不认识当前 CPU 架构,最典型的是 ARM 机器。
解决:先用./config.guess单独跑一下确认它输出为空。然后从系统的 automake 目录复制新的config.guess和config.sub覆盖,或者直接用./configure --build=$(uname -m)-unknown-linux-gnu手动指定三元组。第二种方式不改动源码包,重装时可复现,我更倾向它。
5.2 make 阶段报 multiple definition of
现象:编译过程一路正常,到链接.so时刷出一片multiple definition of 'xxx',每个变量名都不一样,但错误形式一致。
原因:gcc 10 开始默认使用-fno-common,而 2.1.21 这种老代码里有大量在头文件里定义全局变量的写法,在老编译器下会被合并,在新编译器下就成了重复定义。
解决:configure 时补CFLAGS="-O2 -fcommon",把老
本文还有配套的精品资源,点击获取