* NEW: Add support for ZFS ACLs on FreeBSD with libsunacl. From FR#83.
* FIX: Active Directory LDAP queries for ACL support with new options
"ldap user filter" and "ldap group filter". Bug #526.
+* NEW: Option "vol dbnest", when set to true, the CNID database for
+ a volume is stored in the volume root of a share in a directory
+ .AppleDB like in Netatalk 2. Defaults to false. From FR#84.
+* FIX: Small fix in the DSI tickle handling. Bug #528.
+* UPD: Enhance handling of connection attempts when hitting the
+ connection limit. Bug #529.
+* FIX: Saving from Word to a folder that is a symlink to a folder on
+ another filesystem results in a crash of the afpd process and
+ the save to fail. This happens only if the option
+ "follow symlinks" is enabled. Bug #532.
+* FIX: Disable Kerberos UAM if AFP service principal name can't be
+ evaluated. Fixes bug #531.
Changes in 3.0.5
================
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>vol dbnest = <replaceable>BOOLEAN</replaceable> (default:
+ <emphasis>no</emphasis>) <type>(G)</type></term>
+
+ <listitem>
+ <para>Setting this option to true brings back Netatalk 2
+ behaviour of storing the CNID database in a folder called
+ .AppleDB inside the volume root of each share.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>volnamelen = <replaceable>number</replaceable>
<type>(G)</type></term>
Setting the option to true causes afpd to follow symlinks on the
server. symlinks may point outside of the AFP volume, currently
afpd doesn't do any checks for "wide symlinks".</para>
+ <note>
+ <para>This option will subtly break when the symlinks point
+ across filesystem boundaries.</para>
+ </note>
</listitem>
</varlistentry>
int family, s;
static char interfaddr[NI_MAXHOST];
- auth_load(obj->options.uampath, obj->options.uamlist);
+ auth_load(obj, obj->options.uampath, obj->options.uamlist);
set_signature(&obj->options);
#ifdef HAVE_LDAP
acl_ldap_freeconfig();
return;
}
- if ((err = pollvoltime(AFPobj)) == 0)
+ if ((err = pollvoltime(AFPobj)) == 0) {
LOG(log_debug, logtype_afpd, "afp_alarm: sending DSI tickle");
err = dsi_tickle(AFPobj->dsi);
+ }
if (err <= 0) {
if (geteuid() == 0) {
LOG(log_note, logtype_afpd, "afp_alarm: unauthenticated user, connection problem");
/*
* Its easier to use asprintf to set the TXT record values
*/
-#define TXTRecordPrintf(rec, key, args...) { \
+#define TXTRecordPrintf(rec, key, args, ...) { \
char *str; \
asprintf(&str, args); \
TXTRecordSetValue(rec, key, strlen(str), str); \
free(str); \
}
-#define TXTRecordKeyPrintf(rec, k, var, args...) { \
+#define TXTRecordKeyPrintf(rec, k, var, args, ...) { \
char *key, *str; \
asprintf(&key, k, var); \
asprintf(&str, args); \
return AFPERR_NOTAUTH;
}
+ if (obj->cnx_cnt > obj->cnx_max) {
+ LOG(log_error, logtype_dsi, "login: too many connections, limit: %d", obj->cnx_max);
+ return AFPERR_MAXSESS;
+ }
+
LOG(log_note, logtype_afpd, "%s Login by %s",
afp_versions[afp_version_index].av_name, pwd->pw_name);
}
/* load all of the modules */
-int auth_load(const char *path, const char *list)
+int auth_load(AFPObj *obj, const char *path, const char *list)
{
char name[MAXPATHLEN + 1], buf[MAXPATHLEN + 1], *p;
struct uam_mod *mod;
if ((stat(name, &st) == 0) && (mod = uam_load(name, p))) {
*/
if (stat(name, &st) == 0) {
- if ((mod = uam_load(name, p))) {
+ if ((mod = uam_load(obj, name, p))) {
uam_attach(&uam_modules, mod);
LOG(log_debug, logtype_afpd, "uam: %s loaded", p);
} else {
return (data - begin);
}
-static bool append_directoryname(char **pdata,
- size_t offset,
- size_t *size,
- char* DirectoryNamesCount,
- size_t len,
- char *principal)
-{
- if (sizeof(uint8_t) + len > maxstatuslen - offset - *size) {
- LOG(log_error, logtype_afpd,
- "status:DirectoryNames: out of space for principal '%s' (len=%d)",
- principal, len);
- return false;
- } else if (len > 255) {
- LOG(log_error, logtype_afpd,
- "status:DirectoryNames: principal '%s' (len=%d) too long (max=255)",
- principal, len);
- return false;
- }
-
- LOG(log_info, logtype_afpd,
- "DirectoryNames[%d]=%s",
- *DirectoryNamesCount, principal);
-
- *DirectoryNamesCount += 1;
- char *data = *pdata;
- *data++ = len;
- strncpy(data, principal, len);
-
- *pdata += len + 1;
- *size += sizeof(uint8_t) + len;
-
- return true;
-}
/**
* DirectoryNamesCount offset: uint16_t
static size_t status_directorynames(char *data,
int *diroffset,
const DSI *dsi _U_,
- const struct afp_options *options _U_)
+ const struct afp_options *options)
{
char *begin = data;
uint16_t offset;
offset = ntohs(offset);
data += offset;
- char *DirectoryNamesCount = data++;
- size_t size = sizeof(uint8_t);
- *DirectoryNamesCount = 0;
-
- if (!uam_gss_enabled())
- goto offset_calc;
-
-#ifdef HAVE_KERBEROS
- krb5_context context;
- krb5_error_code ret;
- const char *error_msg;
-
- if (krb5_init_context(&context)) {
- LOG(log_error, logtype_afpd,
- "status:DirectoryNames failed to intialize a krb5_context");
- goto offset_calc;
- }
-
- krb5_keytab keytab;
- if ((ret = krb5_kt_default(context, &keytab)))
- goto krb5_error;
-
- // figure out which service principal to use
- krb5_keytab_entry entry;
- char *principal;
- if (options->k5service && options->fqdn && options->k5realm) {
- LOG(log_debug, logtype_afpd,
- "status:DirectoryNames: using service principal specified in options");
-
- krb5_principal service_principal;
- if ((ret = krb5_build_principal(context,
- &service_principal,
- strlen(options->k5realm),
- options->k5realm,
- options->k5service,
- options->fqdn,
- NULL)))
- goto krb5_error;
-
- // try to get the given service principal from keytab
- ret = krb5_kt_get_entry(context,
- keytab,
- service_principal,
- 0, // kvno - wildcard
- 0, // enctype - wildcard
- &entry);
- if (ret == KRB5_KT_NOTFOUND) {
- krb5_unparse_name(context, service_principal, &principal);
- LOG(log_error, logtype_afpd,
- "status:DirectoryNames: specified service principal '%s' not found in keytab",
- principal);
- // XXX: should this be krb5_xfree?
-#ifdef HAVE_KRB5_FREE_UNPARSED_NAME
- krb5_free_unparsed_name(context, principal);
-#else
- krb5_xfree(principal);
-#endif
- goto krb5_cleanup;
- }
- krb5_free_principal(context, service_principal);
- if (ret)
- goto krb5_error;
+ if (!uam_gss_enabled() || !options->k5principal) {
+ *data = 0;
+ data++;
} else {
- LOG(log_debug, logtype_afpd,
- "status:DirectoryNames: using first entry from keytab as service principal");
-
- krb5_kt_cursor cursor;
- if ((ret = krb5_kt_start_seq_get(context, keytab, &cursor)))
- goto krb5_error;
-
- ret = krb5_kt_next_entry(context, keytab, &entry, &cursor);
- krb5_kt_end_seq_get(context, keytab, &cursor);
- if (ret)
- goto krb5_error;
+ memcpy(data, options->k5principal, options->k5principal_buflen);
+ data += options->k5principal_buflen;
}
- krb5_unparse_name(context, entry.principal, &principal);
-#ifdef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS
- krb5_free_keytab_entry_contents(context, &entry);
-#elif defined(HAVE_KRB5_KT_FREE_ENTRY)
- krb5_kt_free_entry(context, &entry);
-#endif
- append_directoryname(&data,
- offset,
- &size,
- DirectoryNamesCount,
- strlen(principal),
- principal);
-
- free(principal);
- goto krb5_cleanup;
-
-krb5_error:
- if (ret) {
- error_msg = krb5_get_error_message(context, ret);
- LOG(log_note, logtype_afpd, "Can't get principal from default keytab: %s",
- (char *)error_msg);
-#ifdef HAVE_KRB5_FREE_ERROR_MESSAGE
- krb5_free_error_message(context, error_msg);
-#else
- krb5_xfree(error_msg);
-#endif
- }
-
-krb5_cleanup:
- krb5_kt_close(context, keytab);
- krb5_free_context(context);
-#else
- if (!options->k5service || !options->fqdn || !options->k5realm)
- goto offset_calc;
-
- char principal[255];
- size_t len = snprintf(principal, sizeof(principal), "%s/%s@%s",
- options->k5service, options->fqdn, options->k5realm);
-
- append_directoryname(&data,
- offset,
- &size,
- DirectoryNamesCount,
- strlen(principal),
- principal);
-#endif // HAVE_KERBEROS
-
-offset_calc:
/* Calculate and store offset for UTF8ServerName */
*diroffset += sizeof(uint16_t);
offset = htons(data - begin);
/* --- server uam functions -- */
/* uam_load. uams must have a uam_setup function. */
-struct uam_mod *uam_load(const char *path, const char *name)
+struct uam_mod *uam_load(AFPObj *obj, const char *path, const char *name)
{
char buf[MAXPATHLEN + 1], *p;
struct uam_mod *mod;
/* version check would go here */
if (!mod->uam_fcn->uam_setup ||
- ((*mod->uam_fcn->uam_setup)(name) < 0)) {
+ ((*mod->uam_fcn->uam_setup)(obj, name) < 0)) {
LOG(log_error, logtype_afpd, "uam_load(%s): uam_setup failed", name);
goto uam_load_err;
}
(a)->uam_next->uam_prev = (a)->uam_prev; \
} while (0)
-extern struct uam_mod *uam_load (const char *, const char *);
+extern struct uam_mod *uam_load (AFPObj *, const char *, const char *);
extern void uam_unload (struct uam_mod *);
/* auth.c */
-int auth_load (const char *, const char *);
+int auth_load (AFPObj *, const char *, const char *);
int auth_register (const int, struct uam_obj *);
#define auth_unregister(a) uam_detach(a)
struct uam_obj *auth_uamfind (const int, const char *, const int);
return ret;
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", pam_login,
pam_logincont, pam_logout, pam_login_ext) < 0)
return ret;
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHX2", passwd_login,
passwd_logincont, NULL, passwd_login_ext) < 0)
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHCAST128", pam_login,
pam_logincont, pam_logout, pam_login_ext) < 0)
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "DHCAST128",
passwd_login, passwd_logincont, NULL, passwd_login_ext) < 0)
#include <atalk/uam.h>
#include <atalk/util.h>
#include <atalk/compat.h>
+#include <atalk/globals.h>
/* Kerberos includes */
#ifdef HAVE_GSSAPI_GSSAPI_H
#include <gssapi.h>
#endif // HAVE_GSSAPI_GSSAPI_H
+#ifdef HAVE_KERBEROS
+#ifdef HAVE_KRB5_KRB5_H
+#include <krb5/krb5.h>
+#else
+#include <krb5.h>
+#endif /* HAVE_KRB5_KRB5_H */
+#endif /* HAVE_KERBEROS */
+
#define LOG_UAMS(log_level, ...) \
LOG(log_level, logtype_uams, __VA_ARGS__)
return gss_login(obj, uam_pwd, ibuf, ibuflen, rbuf, rbuflen);
}
-static int uam_setup(const char *path)
+static int set_principal(AFPObj *obj, char *principal)
{
+ size_t len = strlen(principal);
+
+ if (len > 255) {
+ LOG(log_error, logtype_afpd, "set_principal: principal '%s' too long (max=255)", principal, len);
+ return -1;
+ }
+
+ /* We store: 1 byte number principals (1) + 1 byte principal name length + zero terminated principal */
+ if ((obj->options.k5principal = malloc(len + 3)) == NULL) {
+ LOG(log_error, logtype_afpd, "set_principal: OOM");
+ return -1;
+ }
+
+ LOG(log_info, logtype_afpd, "Using AFP Kerberos service principal name: %s", principal);
+
+ obj->options.k5principal[0] = 1;
+ obj->options.k5principal[1] = (unsigned char)len;
+ obj->options.k5principal_buflen = len + 2;
+ strncpy(obj->options.k5principal + 2, principal, len);
+
+ return 0;
+}
+
+static int gss_create_principal(AFPObj *obj)
+{
+ int rv = -1;
+#ifdef HAVE_KERBEROS
+ krb5_context context;
+ krb5_error_code ret;
+ const char *error_msg;
+ krb5_keytab keytab;
+ krb5_keytab_entry entry;
+ krb5_principal service_principal;
+ char *principal;
+ krb5_kt_cursor cursor;
+
+ if (krb5_init_context(&context)) {
+ LOG(log_error, logtype_afpd, "gss_create_principal: failed to intialize a krb5_context");
+ goto exit;
+ }
+ if ((ret = krb5_kt_default(context, &keytab)))
+ goto krb5_error;
+
+ if (obj->options.k5service && obj->options.fqdn && obj->options.k5realm) {
+ LOG(log_debug, logtype_afpd, "gss_create_principal: using service principal specified in options");
+
+ if ((ret = krb5_build_principal(context,
+ &service_principal,
+ strlen(obj->options.k5realm),
+ obj->options.k5realm,
+ obj->options.k5service,
+ obj->options.fqdn,
+ NULL)))
+ goto krb5_error;
+
+ if ((ret = krb5_kt_get_entry(context,
+ keytab,
+ service_principal,
+ 0, // kvno - wildcard
+ 0, // enctype - wildcard
+ &entry)) == KRB5_KT_NOTFOUND) {
+ krb5_unparse_name(context, service_principal, &principal);
+ LOG(log_error, logtype_afpd, "gss_create_principal: specified service principal '%s' not found in keytab", principal);
+#ifdef HAVE_KRB5_FREE_UNPARSED_NAME
+ krb5_free_unparsed_name(context, principal);
+#else
+ krb5_xfree(principal);
+#endif
+ goto krb5_cleanup;
+ }
+ krb5_free_principal(context, service_principal);
+ if (ret)
+ goto krb5_error;
+ } else {
+ LOG(log_debug, logtype_afpd, "gss_create_principal: using first entry from keytab as service principal");
+ if ((ret = krb5_kt_start_seq_get(context, keytab, &cursor)))
+ goto krb5_error;
+ ret = krb5_kt_next_entry(context, keytab, &entry, &cursor);
+ krb5_kt_end_seq_get(context, keytab, &cursor);
+ if (ret)
+ goto krb5_error;
+ }
+
+ krb5_unparse_name(context, entry.principal, &principal);
+#ifdef HAVE_KRB5_FREE_KEYTAB_ENTRY_CONTENTS
+ krb5_free_keytab_entry_contents(context, &entry);
+#elif defined(HAVE_KRB5_KT_FREE_ENTRY)
+ krb5_kt_free_entry(context, &entry);
+#endif
+ set_principal(obj, principal);
+ free(principal);
+ rv = 0;
+ goto krb5_cleanup;
+
+krb5_error:
+ if (ret) {
+ error_msg = krb5_get_error_message(context, ret);
+ LOG(log_note, logtype_afpd, "Can't get principal from default keytab: %s",
+ (char *)error_msg);
+#ifdef HAVE_KRB5_FREE_ERROR_MESSAGE
+ krb5_free_error_message(context, error_msg);
+#else
+ krb5_xfree(error_msg);
+#endif
+ }
+
+krb5_cleanup:
+ krb5_kt_close(context, keytab);
+ krb5_free_context(context);
+
+#else /* ! HAVE_KERBEROS */
+
+ if (!options->k5service || !options->fqdn || !options->k5realm)
+ goto exit;
+
+ char principal[255];
+ size_t len = snprintf(principal, sizeof(principal), "%s/%s@%s",
+ options->k5service, options->fqdn, options->k5realm);
+ (void)set_principal(&obj, principal);
+ rv = 0;
+#endif /* HAVE_KERBEROS */
+
+exit:
+ return rv;
+}
+
+static int uam_setup(void *handle, const char *path)
+{
+ AFPObj *obj = (AFPObj *)handle;
+ if (gss_create_principal(obj) != 0)
+ return -1;
+
return uam_register(UAM_SERVER_LOGIN_EXT, path, "Client Krb v2",
gss_login, gss_logincont, gss_logout, gss_login_ext);
}
}
-static int uam_setup(const char *path)
+static int uam_setup(void *handle, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "No User Authent",
noauth_login, NULL, NULL, noauth_login_ext) < 0)
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Cleartxt Passwrd",
pam_login, NULL, pam_logout, pam_login_ext) < 0)
return(0);
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Cleartxt Passwrd",
passwd_login, NULL, NULL, passwd_login_ext) < 0)
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN, path, "PGPuam 1.0",
pgp_login, pgp_logincont, NULL) < 0)
return (rand_login(obj, username, ulen, uam_pwd, ibuf, ibuflen, rbuf, rbuflen));
}
-static int uam_setup(const char *path)
+static int uam_setup(void *obj, const char *path)
{
if (uam_register(UAM_SERVER_LOGIN_EXT, path, "Randnum exchange",
randnum_login, randnum_logincont, NULL, randnum_login_ext) < 0)
#define AFPSRVRINFO_FASTBOZO (1<<15) /* fast copying */
#define AFP_OK 0
+#define AFPERR_MAXSESS -1068 /* maximum number of allowed sessions reached */
#define AFPERR_DID1 -4000 /* not an afp error DID is 1*/
#define AFPERR_ACCESS -5000 /* permission denied */
#define AFPERR_AUTHCONT -5001 /* logincont */
char *signatureopt;
unsigned char signature[16];
char *k5service, *k5realm, *k5keytab;
+ size_t k5principal_buflen;
+ char *k5principal;
char *unixcodepage, *maccodepage, *volcodepage;
charset_t maccharset, unixcharset;
mode_t umask;
gid_t *groups;
int ngroups;
int afp_version;
+ int cnx_cnt, cnx_max;
/* Functions */
void (*logout)(void);
void (*exit)(int);
* support is braindead. it also allows me to do a little versioning. */
struct uam_export {
int uam_type, uam_version;
- int (*uam_setup)(const char *);
+ int (*uam_setup)(void *, const char *);
void (*uam_cleanup)(void);
};
mode_t mode = 0;
if (dirfd != -1) {
- if ((cwdfd = open(".", O_RDONLY) == -1) || (fchdir(dirfd) != 0))
+ if (((cwdfd = open(".", O_RDONLY)) == -1) || (fchdir(dirfd) != 0))
EC_FAIL;
}
LOG(log_error, logtype_dsi, "dsi_getsess: %s", strerror(errno));
close(ipc_fds[0]);
dsi->header.dsi_flags = DSIFL_REPLY;
- dsi->header.dsi_data.dsi_code = DSIERR_SERVBUSY;
+ dsi->header.dsi_data.dsi_code = htonl(DSIERR_SERVBUSY);
dsi_send(dsi);
dsi->header.dsi_data.dsi_code = DSIERR_OK;
kill(pid, SIGKILL);
return 0;
}
- /* child: check number of open connections. this is one off the
- * actual count. */
- if ((serv_children->servch_count >= serv_children->servch_nsessions) &&
- (dsi->header.dsi_command == DSIFUNC_OPEN)) {
- LOG(log_info, logtype_dsi, "dsi_getsess: too many connections");
- dsi->header.dsi_flags = DSIFL_REPLY;
- dsi->header.dsi_data.dsi_code = DSIERR_TOOMANY;
- dsi_send(dsi);
- exit(EXITERR_CLNT);
- }
+ /* Save number of existing and maximum connections */
+ dsi->AFPobj->cnx_cnt = serv_children->servch_count;
+ dsi->AFPobj->cnx_max = serv_children->servch_nsessions;
/* get rid of some stuff */
dsi->AFPobj->ipc_fd = ipc_fds[1];
for(i = 0; i < vlen; i++)
if(tmpname[i] == '/') tmpname[i] = ':';
- bstring dbpath;
- EC_NULL( val = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol dbpath", _PATH_STATEDIR "CNID/") );
- EC_NULL( dbpath = bformat("%s/%s/", val, tmpname) );
- EC_NULL( volume->v_dbpath = strdup(cfrombstr(dbpath)) );
- bdestroy(dbpath);
+
+ if (!atalk_iniparser_getboolean(obj->iniconfig, INISEC_GLOBAL, "vol dbnest", 0)) {
+ bstring dbpath;
+ EC_NULL( val = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol dbpath", _PATH_STATEDIR "CNID/") );
+ EC_NULL( dbpath = bformat("%s/%s/", val, tmpname) );
+ EC_NULL( volume->v_dbpath = strdup(cfrombstr(dbpath)) );
+ bdestroy(dbpath);
+ } else {
+ EC_NULL( volume->v_dbpath = strdup(path) );
+ }
if ((val = getoption(obj->iniconfig, section, "cnid scheme", preset, NULL)))
EC_NULL( volume->v_cnidscheme = strdup(val) );
CONFIG_ARG_FREE(obj->options.k5service);
if (obj->options.k5realm)
CONFIG_ARG_FREE(obj->options.k5realm);
+ if (obj->options.k5principal)
+ CONFIG_ARG_FREE(obj->options.k5principal);
if (obj->options.listen)
CONFIG_ARG_FREE(obj->options.listen);
if (obj->options.interfaces)
@localstatedir@/netatalk/CNID/\&.
.RE
.PP
+vol dbnest = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR
+.RS 4
+Setting this option to true brings back Netatalk 2 behaviour of storing the CNID database in a folder called \&.AppleDB inside the volume root of each share\&.
+.RE
+.PP
volnamelen = \fInumber\fR \fB(G)\fR
.RS 4
Max length of UTF8\-MAC volume name for Mac OS X\&. Note that Hangul is especially sensitive to this\&.
follow symlinks = \fIBOOLEAN\fR (default: \fIno\fR) \fB(V)\fR
.RS 4
The default setting is false thus symlinks are not followed on the server\&. This is the same behaviour as OS X\*(Aqs AFP server\&. Setting the option to true causes afpd to follow symlinks on the server\&. symlinks may point outside of the AFP volume, currently afpd doesn\*(Aqt do any checks for "wide symlinks"\&.
+.if n \{\
+.sp
+.\}
+.RS 4
+.it 1 an-trap
+.nr an-no-space-flag 1
+.nr an-break-flag 1
+.br
+.ps +1
+\fBNote\fR
+.ps -1
+.br
+This option will subtly break when the symlinks point across filesystem boundaries\&.
+.sp .5v
+.RE
.RE
.PP
invisible dots = \fIBOOLEAN\fR (default: \fIno\fR) \fB(V)\fR