| /* | |
| * BRLTTY - A background process providing access to the console screen (when in | |
| * text mode) for a blind person using a refreshable braille display. | |
| * | |
| * Copyright (C) 1995-2023 by The BRLTTY Developers. | |
| * | |
| * BRLTTY comes with ABSOLUTELY NO WARRANTY. | |
| * | |
| * This is free software, placed under the terms of the | |
| * GNU Lesser General Public License, as published by the Free Software | |
| * Foundation; either version 2.1 of the License, or (at your option) any | |
| * later version. Please see the file LICENSE-LGPL for details. | |
| * | |
| * Web Page: http://brltty.app/ | |
| * | |
| * This software is maintained by Dave Mielke <dave@mielke.cc>. | |
| */ | |
| #include "prologue.h" | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #if defined(__MINGW32__) | |
| #include <ws2tcpip.h> | |
| #elif defined(__MSDOS__) | |
| #elif defined(GRUB_RUNTIME) | |
| typedef unsigned int uid_t; | |
| typedef unsigned int gid_t; | |
| #else /* Unix */ | |
| #include <sys/socket.h> | |
| #include <sys/un.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <netdb.h> | |
| #include <pwd.h> | |
| #include <grp.h> | |
| #endif /* platform-specific includes */ | |
| #if !defined(AF_LOCAL) && defined(AF_UNIX) | |
| #define AF_LOCAL AF_UNIX | |
| #endif /* !defined(AF_LOCAL) && defined(AF_UNIX) */ | |
| #if !defined(PF_LOCAL) && defined(PF_UNIX) | |
| #define PF_LOCAL PF_UNIX | |
| #endif /* !defined(PF_LOCAL) && defined(PF_UNIX) */ | |
| #include "log.h" | |
| #include "strfmt.h" | |
| #include "parse.h" | |
| #include "auth.h" | |
| #include "async_wait.h" | |
| /* peer credentials */ | |
| #undef CAN_CHECK_CREDENTIALS | |
| typedef struct { | |
| #ifdef __MINGW32__ | |
| const char *name; | |
| #else /* __MINGW32__ */ | |
| uid_t id; | |
| #endif /* __MINGW32__ */ | |
| } MethodDescriptor_user; | |
| typedef struct { | |
| #ifdef __MINGW32__ | |
| const char *name; | |
| #else /* __MINGW32__ */ | |
| gid_t id; | |
| #endif /* __MINGW32__ */ | |
| } MethodDescriptor_group; | |
| #if defined(__MINGW32__) | |
| #define CAN_CHECK_CREDENTIALS | |
| typedef struct { | |
| char *user; | |
| } PeerCredentials; | |
| static int | |
| retrievePeerCredentials (PeerCredentials *credentials, FileDescriptor fd) { | |
| char buffer[0X100+1]; | |
| if (GetNamedPipeHandleState(fd, NULL, NULL, NULL, NULL, buffer, sizeof(buffer))) { | |
| buffer[sizeof(buffer) - 1] = 0; | |
| if ((credentials->user = strdup(buffer))) { | |
| return 1; | |
| } | |
| } else { | |
| switch (GetLastError()) { | |
| default: | |
| logWindowsSystemError("GetNamedPipeHandleState"); | |
| case ERROR_INSUFFICIENT_BUFFER: /* buffer too small */ | |
| case ERROR_INVALID_HANDLE: /* not a named pipe */ | |
| case ERROR_CANNOT_IMPERSONATE: /* no data transferred yet */ | |
| break; | |
| } | |
| } | |
| return 0; | |
| } | |
| static void | |
| releasePeerCredentials (PeerCredentials *credentials) { | |
| free(credentials->user); | |
| } | |
| static int | |
| checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) { | |
| return strcmp(user->name, credentials->user) == 0; | |
| } | |
| static int | |
| checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) { | |
| return 0; | |
| } | |
| #elif defined(HAVE_GETPEERUCRED) | |
| #define CAN_CHECK_CREDENTIALS | |
| #include <ucred.h> | |
| #ifdef HAVE_GETZONEID | |
| #include <zone.h> | |
| #endif /* HAVE_GETZONEID */ | |
| typedef ucred_t *PeerCredentials; | |
| static int | |
| retrievePeerCredentials (PeerCredentials *credentials, int fd) { | |
| *credentials = NULL; | |
| if (getpeerucred(fd, credentials) == -1) { | |
| logSystemError("getpeerucred"); | |
| return 0; | |
| } | |
| #ifdef HAVE_GETZONEID | |
| if (ucred_getzoneid(*credentials) != getzoneid()) { | |
| ucred_free(*credentials); | |
| return 0; | |
| } | |
| #endif /* HAVE_GETZONEID */ | |
| return 1; | |
| } | |
| static void | |
| releasePeerCredentials (PeerCredentials *credentials) { | |
| ucred_free(*credentials); | |
| } | |
| static int | |
| checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) { | |
| if (user->id == ucred_geteuid(*credentials)) return 1; | |
| return 0; | |
| } | |
| static int | |
| checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) { | |
| if (group->id == ucred_getegid(*credentials)) return 1; | |
| { | |
| const gid_t *groups; | |
| int count = ucred_getgroups(*credentials, &groups); | |
| while (count > 0) | |
| if (group->id == groups[--count]) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| #elif defined(HAVE_GETPEEREID) | |
| #define CAN_CHECK_CREDENTIALS | |
| typedef struct { | |
| uid_t euid; | |
| gid_t egid; | |
| } PeerCredentials; | |
| static int | |
| retrievePeerCredentials (PeerCredentials *credentials, int fd) { | |
| if (getpeereid(fd, &credentials->euid, &credentials->egid) != -1) return 1; | |
| logSystemError("getpeereid"); | |
| return 0; | |
| } | |
| static void | |
| releasePeerCredentials (PeerCredentials *credentials) { | |
| } | |
| static int | |
| checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) { | |
| return user->id == credentials->euid; | |
| } | |
| static int | |
| checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) { | |
| return group->id == credentials->egid; | |
| } | |
| #elif defined(SO_PEERCRED) | |
| #define CAN_CHECK_CREDENTIALS | |
| typedef struct ucred PeerCredentials; | |
| static int | |
| retrievePeerCredentials (PeerCredentials *credentials, int fd) { | |
| socklen_t length = sizeof(*credentials); | |
| if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, credentials, &length) != -1) return 1; | |
| logSystemError("getsockopt[SO_PEERCRED]"); | |
| return 0; | |
| } | |
| static void | |
| releasePeerCredentials (PeerCredentials *credentials) { | |
| } | |
| static int | |
| checkPeerUser (PeerCredentials *credentials, const MethodDescriptor_user *user) { | |
| return user->id == credentials->uid; | |
| } | |
| static int | |
| checkPeerGroup (PeerCredentials *credentials, const MethodDescriptor_group *group) { | |
| return group->id == credentials->gid; | |
| } | |
| #else /* peer credentials method */ | |
| #warning peer credentials support not available on this platform | |
| #endif /* peer credentials method */ | |
| /* general type definitions */ | |
| typedef int (*MethodPerform) (AuthDescriptor *auth, FileDescriptor fd, void *data); | |
| typedef struct { | |
| const char *name; | |
| void *(*initialize) (const char *parameter); | |
| void (*release) (void *data); | |
| MethodPerform client; | |
| MethodPerform server; | |
| } MethodDefinition; | |
| typedef struct { | |
| const MethodDefinition *definition; | |
| void *data; | |
| } MethodDescriptor; | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| typedef enum { | |
| PCS_NEED, | |
| PCS_CANT, | |
| PCS_HAVE | |
| } PeerCredentialsState; | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| typedef int (*AuthPerform) (AuthDescriptor *auth, FileDescriptor fd); | |
| struct AuthDescriptorStruct { | |
| int count; | |
| char **parameters; | |
| MethodDescriptor *methods; | |
| AuthPerform perform; | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| PeerCredentialsState peerCredentialsState; | |
| PeerCredentials peerCredentials; | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| }; | |
| /* the keyfile method */ | |
| typedef struct { | |
| const char *path; | |
| } MethodDescriptor_keyfile; | |
| static void * | |
| authKeyfile_initialize (const char *parameter) { | |
| MethodDescriptor_keyfile *keyfile; | |
| if ((keyfile = malloc(sizeof(*keyfile)))) { | |
| if (*parameter) { | |
| keyfile->path = parameter; | |
| return keyfile; | |
| } else { | |
| logMessage(LOG_ERR, "path to key file not specified"); | |
| } | |
| free(keyfile); | |
| } else { | |
| logMallocError(); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| authKeyfile_release (void *data) { | |
| MethodDescriptor_keyfile *keyfile = data; | |
| free(keyfile); | |
| } | |
| static int | |
| authKeyfile_client (AuthDescriptor *auth, FileDescriptor fd, void *data) { | |
| return 1; | |
| } | |
| static int | |
| authKeyfile_server (AuthDescriptor *auth, FileDescriptor fd, void *data) { | |
| MethodDescriptor_keyfile *keyfile = data; | |
| logMessage(LOG_CATEGORY(SERVER_EVENTS), "checking key file: %s", keyfile->path); | |
| return 1; | |
| } | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| static int | |
| getPeerCredentials (AuthDescriptor *auth, FileDescriptor fd) { | |
| if (auth->peerCredentialsState == PCS_NEED) { | |
| auth->peerCredentialsState = retrievePeerCredentials(&auth->peerCredentials, fd)? PCS_HAVE: PCS_CANT; | |
| } | |
| return auth->peerCredentialsState == PCS_HAVE; | |
| } | |
| /* the user method */ | |
| static void * | |
| authUser_initialize (const char *parameter) { | |
| MethodDescriptor_user *user; | |
| if ((user = malloc(sizeof(*user)))) { | |
| #ifdef __MINGW32__ | |
| user->name = parameter; | |
| return user; | |
| #else /* __MINGW32__ */ | |
| if (!*parameter) { | |
| user->id = geteuid(); | |
| return user; | |
| } | |
| { | |
| int value; | |
| if (isInteger(&value, parameter)) { | |
| user->id = value; | |
| return user; | |
| } | |
| } | |
| { | |
| const struct passwd *p = getpwnam(parameter); | |
| if (p) { | |
| user->id = p->pw_uid; | |
| return user; | |
| } | |
| } | |
| logMessage(LOG_ERR, "unknown user: %s", parameter); | |
| free(user); | |
| #endif /* __MINGW32__ */ | |
| } else { | |
| logMallocError(); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| authUser_release (void *data) { | |
| MethodDescriptor_user *user = data; | |
| free(user); | |
| } | |
| static int | |
| authUser_server (AuthDescriptor *auth, FileDescriptor fd, void *data) { | |
| MethodDescriptor_user *user = data; | |
| return getPeerCredentials(auth, fd) && | |
| checkPeerUser(&auth->peerCredentials, user); | |
| } | |
| /* the group method */ | |
| static void * | |
| authGroup_initialize (const char *parameter) { | |
| MethodDescriptor_group *group; | |
| if ((group = malloc(sizeof(*group)))) { | |
| #ifdef __MINGW32__ | |
| group->name = parameter; | |
| return group; | |
| #else /* __MINGW32__ */ | |
| if (!*parameter) { | |
| group->id = getegid(); | |
| return group; | |
| } | |
| { | |
| int value; | |
| if (isInteger(&value, parameter)) { | |
| group->id = value; | |
| return group; | |
| } | |
| } | |
| { | |
| const struct group *g = getgrnam(parameter); | |
| if (g) { | |
| group->id = g->gr_gid; | |
| return group; | |
| } | |
| } | |
| logMessage(LOG_ERR, "unknown group: %s", parameter); | |
| free(group); | |
| #endif /* __MINGW32__ */ | |
| } else { | |
| logMallocError(); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| authGroup_release (void *data) { | |
| MethodDescriptor_group *group = data; | |
| free(group); | |
| } | |
| static int | |
| authGroup_server (AuthDescriptor *auth, FileDescriptor fd, void *data) { | |
| MethodDescriptor_group *group = data; | |
| return getPeerCredentials(auth, fd) && | |
| checkPeerGroup(&auth->peerCredentials, group); | |
| } | |
| #ifdef HAVE_POLKIT | |
| #include <polkit/polkit.h> | |
| typedef struct { | |
| PolkitAuthority *authority; | |
| } MethodDescriptor_polkit; | |
| static void * | |
| authPolkit_initialize (const char *parameter) { | |
| MethodDescriptor_polkit *polkit; | |
| if ((polkit = malloc(sizeof(*polkit)))) { | |
| memset(polkit, 0, sizeof(*polkit)); | |
| while (1) { | |
| GError *error = NULL; | |
| polkit->authority = polkit_authority_get_sync(NULL, &error); | |
| if (polkit->authority) return polkit; | |
| int wait = 0; | |
| char message[0X100]; | |
| STR_BEGIN(message, sizeof(message)); | |
| STR_PRINTF("unable to connect to polkit"); | |
| if (error) { | |
| GQuark domain = error->domain; | |
| gint code = error->code; | |
| STR_PRINTF(": %s (%d) %s (%d)", | |
| g_quark_to_string(domain), (int)domain, | |
| error->message, code); | |
| g_error_free(error); | |
| error = NULL; | |
| if ((domain == G_IO_ERROR) || (code == G_IO_ERROR_NOT_FOUND)) wait = 1; | |
| } | |
| STR_END; | |
| logMessage(LOG_WARNING, "%s", message); | |
| if (!wait) break; | |
| asyncWait(1000); | |
| } | |
| g_free(polkit); | |
| } else { | |
| logMallocError(); | |
| } | |
| return NULL; | |
| } | |
| static void | |
| authPolkit_release (void *data) { | |
| MethodDescriptor_polkit *polkit = data; | |
| g_object_unref (polkit->authority); | |
| free(polkit); | |
| } | |
| static int | |
| authPolkit_server (AuthDescriptor *auth, FileDescriptor fd, void *data) { | |
| MethodDescriptor_polkit *polkit = data; | |
| struct ucred cred; | |
| socklen_t length = sizeof(cred); | |
| if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &length) != -1) { | |
| logMessage(LOG_CATEGORY(SERVER_EVENTS), | |
| "attempting to authorize client (pid %d, uid %d) via polkit", | |
| cred.pid, cred.uid | |
| ); | |
| if (cred.uid == -1) { | |
| logMessage(LOG_CATEGORY(SERVER_EVENTS), | |
| "user not specified in credentials" | |
| ); | |
| } else { | |
| PolkitSubject *subject = polkit_unix_process_new_for_owner(cred.pid, 0, cred.uid); | |
| if (subject) { | |
| GError *error_local = NULL; | |
| PolkitAuthorizationResult *result = polkit_authority_check_authorization_sync( | |
| polkit->authority, /* authority */ | |
| subject, /* PolkitSubject for client */ | |
| "org.a11y.brlapi.write-display", /* name of polkit action */ | |
| NULL, /* details */ | |
| POLKIT_CHECK_AUTHORIZATION_FLAGS_NONE, /* disallow interaction */ | |
| NULL, /* GCancellable */ | |
| &error_local /* returned error */ | |
| ); | |
| if (result) { | |
| int isAuthorized = polkit_authorization_result_get_is_authorized(result); | |
| g_object_unref(result); | |
| logMessage(LOG_CATEGORY(SERVER_EVENTS), | |
| "polkit_authority_check_authorization_sync returned %d", | |
| isAuthorized | |
| ); | |
| return isAuthorized; | |
| } else { | |
| logMessage(LOG_ERR, "polkit_authority_check_authorization_sync error: %s", error_local->message); | |
| g_error_free(error_local); | |
| } | |
| g_object_unref(subject); | |
| } else { | |
| logSystemError("polkit_unix_process_new_for_owner"); | |
| } | |
| } | |
| } else { | |
| logSystemError("getsockopt[SO_PEERCRED]"); | |
| } | |
| return 0; | |
| } | |
| #endif /* HAVE_POLKIT */ | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| /* general functions */ | |
| static const MethodDefinition methodDefinitions[] = { | |
| { .name = "keyfile", | |
| .initialize = authKeyfile_initialize, | |
| .release = authKeyfile_release, | |
| .client = authKeyfile_client, | |
| .server = authKeyfile_server | |
| }, | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| { .name = "user", | |
| .initialize = authUser_initialize, | |
| .release = authUser_release, | |
| .client = NULL, | |
| .server = authUser_server | |
| }, | |
| { .name = "group", | |
| .initialize = authGroup_initialize, | |
| .release = authGroup_release, | |
| .client = NULL, | |
| .server = authGroup_server | |
| }, | |
| #ifdef HAVE_POLKIT | |
| { .name = "polkit", | |
| .initialize = authPolkit_initialize, | |
| .release = authPolkit_release, | |
| .client = NULL, | |
| .server = authPolkit_server | |
| }, | |
| #endif /* HAVE_POLKIT */ | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| {.name = NULL} | |
| }; | |
| static void | |
| releaseMethodDescriptor (MethodDescriptor *method) { | |
| if (method->data) { | |
| if (method->definition->release) method->definition->release(method->data); | |
| method->data = NULL; | |
| } | |
| } | |
| static void | |
| releaseMethodDescriptors (AuthDescriptor *auth, int count) { | |
| while (count > 0) releaseMethodDescriptor(&auth->methods[--count]); | |
| } | |
| static int | |
| initializeMethodDescriptor (MethodDescriptor *method, const char *parameter) { | |
| const char *name; | |
| int nameLength; | |
| if ((parameter = strchr(name=parameter, ':'))) { | |
| nameLength = parameter++ - name; | |
| } else { | |
| parameter = name + (nameLength = strlen(name)); | |
| } | |
| { | |
| const MethodDefinition *definition = methodDefinitions; | |
| while (definition->name) { | |
| if ((nameLength == strlen(definition->name)) && | |
| (strncmp(name, definition->name, nameLength) == 0)) { | |
| void *data = definition->initialize(parameter); | |
| if (!data) return 0; | |
| method->definition = definition; | |
| method->data = data; | |
| return 1; | |
| } | |
| ++definition; | |
| } | |
| } | |
| logMessage(LOG_WARNING, "unknown authorization method: %.*s", nameLength, name); | |
| return 0; | |
| } | |
| static int | |
| initializeMethodDescriptors (AuthDescriptor *auth) { | |
| int index = 0; | |
| while (index < auth->count) { | |
| if (!initializeMethodDescriptor(&auth->methods[index], auth->parameters[index])) { | |
| releaseMethodDescriptors(auth, index); | |
| return 0; | |
| } | |
| ++index; | |
| } | |
| return 1; | |
| } | |
| static AuthDescriptor * | |
| authBegin (const char *parameter, const char *defaultParameter, AuthPerform perform) { | |
| AuthDescriptor *auth; | |
| if ((auth = malloc(sizeof(*auth)))) { | |
| auth->perform = perform; | |
| if (!parameter) parameter = ""; | |
| if (!*parameter) { | |
| parameter = defaultParameter; | |
| } else if (strcmp(parameter, "none") == 0) { | |
| parameter = ""; | |
| } | |
| if ((auth->parameters = splitString(parameter, '+', &auth->count))) { | |
| if (!auth->count) { | |
| auth->methods = NULL; | |
| return auth; | |
| } | |
| if ((auth->methods = malloc(ARRAY_SIZE(auth->methods, auth->count)))) { | |
| if (initializeMethodDescriptors(auth)) return auth; | |
| free(auth->methods); | |
| } else { | |
| logMallocError(); | |
| } | |
| deallocateStrings(auth->parameters); | |
| } | |
| free(auth); | |
| } else { | |
| logMallocError(); | |
| } | |
| return NULL; | |
| } | |
| static int | |
| authPerformClient (AuthDescriptor *auth, FileDescriptor fd) { | |
| return 1; | |
| } | |
| AuthDescriptor * | |
| authBeginClient (const char *parameter) { | |
| return authBegin(parameter, "", authPerformClient); | |
| } | |
| static int | |
| authPerformServer (AuthDescriptor *auth, FileDescriptor fd) { | |
| int ok = 0; | |
| if (!auth->count) return 1; | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| auth->peerCredentialsState = PCS_NEED; | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| { | |
| int index; | |
| for (index=0; index<auth->count; ++index) { | |
| const MethodDescriptor *method = &auth->methods[index]; | |
| if (!method->definition->server) continue; | |
| if (method->definition->client) continue; | |
| if (method->definition->server(auth, fd, method->data)) { | |
| ok = 1; | |
| break; | |
| } | |
| } | |
| } | |
| #ifdef CAN_CHECK_CREDENTIALS | |
| if (auth->peerCredentialsState != PCS_NEED) { | |
| if (auth->peerCredentialsState == PCS_HAVE) releasePeerCredentials(&auth->peerCredentials); | |
| if (!ok) logMessage(LOG_ERR, "no matching user or group"); | |
| } | |
| #endif /* CAN_CHECK_CREDENTIALS */ | |
| if (!ok) { | |
| int index; | |
| for (index=0; index<auth->count; ++index) { | |
| const MethodDescriptor *method = &auth->methods[index]; | |
| if (!method->definition->server) continue; | |
| if (!method->definition->client) continue; | |
| } | |
| } | |
| return ok; | |
| } | |
| AuthDescriptor * | |
| authBeginServer (const char *parameter) { | |
| return authBegin(parameter, "user", authPerformServer); | |
| } | |
| void | |
| authEnd (AuthDescriptor *auth) { | |
| releaseMethodDescriptors(auth, auth->count); | |
| if (auth->methods) free(auth->methods); | |
| deallocateStrings(auth->parameters); | |
| free(auth); | |
| } | |
| int | |
| authPerform (AuthDescriptor *auth, FileDescriptor fd) { | |
| return auth->perform(auth, fd); | |
| } | |
| void | |
| formatAddress ( | |
| char *buffer, size_t bufferSize, | |
| const void *address, socklen_t addressSize | |
| ) { | |
| #ifdef AF_INET | |
| const struct sockaddr *sa = address; | |
| switch (sa->sa_family) { | |
| #ifndef __MINGW32__ | |
| case AF_LOCAL: { | |
| const struct sockaddr_un *local = address; | |
| if (addressSize <= sizeof(sa_family_t)) { | |
| snprintf(buffer, bufferSize, "local <unnamed>"); | |
| } else { | |
| snprintf(buffer, bufferSize, "local %s", local->sun_path); | |
| } | |
| break; | |
| } | |
| #endif /* __MINGW32__ */ | |
| case AF_INET: { | |
| const struct sockaddr_in *inet = address; | |
| snprintf(buffer, bufferSize, "inet %s:%d", inet_ntoa(inet->sin_addr), ntohs(inet->sin_port)); | |
| break; | |
| } | |
| default: | |
| #if defined(HAVE_GETNAMEINFO) && !defined(WINDOWS) | |
| { | |
| char host[NI_MAXHOST]; | |
| char service[NI_MAXSERV]; | |
| int err; | |
| if (!(err = getnameinfo(address, addressSize, | |
| host, sizeof(host), service, sizeof(service), | |
| NI_NUMERICHOST | NI_NUMERICSERV))) { | |
| snprintf(buffer, bufferSize, "af=%d %s:%s", sa->sa_family, host, service); | |
| break; | |
| } | |
| if (err != EAI_FAMILY) { | |
| #ifdef HAVE_GAI_STRERROR | |
| snprintf(buffer, bufferSize, "reverse lookup error for address family %d: %s", | |
| sa->sa_family, | |
| #ifdef EAI_SYSTEM | |
| (err == EAI_SYSTEM)? strerror(errno): | |
| #endif /* EAI_SYSTEM */ | |
| gai_strerror(err)); | |
| #else /* HAVE_GAI_STRERROR */ | |
| snprintf(buffer, bufferSize, "reverse lookup error %d for address family %d", | |
| err, sa->sa_family); | |
| #endif /* HAVE_GAI_STRERROR */ | |
| break; | |
| } | |
| } | |
| #endif /* GETNAMEINFO */ | |
| { | |
| STR_BEGIN(buffer, bufferSize); | |
| STR_PRINTF("address family %d:", sa->sa_family); | |
| { | |
| const unsigned char *byte = address; | |
| const unsigned char *end = byte + addressSize; | |
| while (byte < end) STR_PRINTF(" %02X", *byte++); | |
| } | |
| STR_END; | |
| } | |
| break; | |
| } | |
| #else /* AF_INET */ | |
| snprintf(buffer, bufferSize, "unknown"); | |
| #endif /* AF_INET */ | |
| } |