/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1999 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: util.c,v 1.42 2006/08/24 01:57:15 paddy_s Exp $
*/
#include "amanda.h"
#include "amutil.h"
#include "match.h"
#include <regex.h>
#include "clock.h"
#include "sockaddr-util.h"
#include "conffile.h"
#include "base64.h"
#include "stream.h"
#include "pipespawn.h"
#include <glib.h>
#include <string.h>
#include "fsusage.h"
GMutex *priv_mutex = NULL;
static int make_socket(sa_family_t family);
static int connect_port(sockaddr_union *addrp, in_port_t port, char *proto,
sockaddr_union *svaddr, int nonblock, int priv,
char **msg);
static int
make_socket(
sa_family_t family)
{
int s;
int save_errno;
#if defined(SO_KEEPALIVE) || defined(USE_REUSEADDR)
int on=1;
int r;
#endif
g_debug("make_socket opening socket with family %d", family);
s = socket(family, SOCK_STREAM, 0);
if (s == -1) {
save_errno = errno;
dbprintf(_("make_socket: socket() failed: %s\n"), strerror(save_errno));
errno = save_errno;
return -1;
}
if (s < 0 || s >= (int)FD_SETSIZE) {
aclose(s);
errno = EMFILE; /* out of range */
return -1;
}
g_debug("make_socket opening socket with family %d: %d", family, s);
#ifdef USE_REUSEADDR
r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (r < 0) {
save_errno = errno;
dbprintf(_("make_socket: setsockopt(SO_REUSEADDR) failed: %s\n"),
strerror(errno));
errno = save_errno;
}
#endif
#ifdef SO_KEEPALIVE
r = setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
(void *)&on, sizeof(on));
if (r == -1) {
save_errno = errno;
dbprintf(_("make_socket: setsockopt() failed: %s\n"),
strerror(save_errno));
aclose(s);
errno = save_errno;
return -1;
}
#endif
return s;
}
GQuark am_util_error_quark(void)
{
return g_quark_from_static_string("am-util-error-quark");
}
/* addrp is my address */
/* svaddr is the address of the remote machine */
/* return socket on success */
/* return -1 on failure */
int
connect_portrange(
sockaddr_union *addrp,
in_port_t first_port,
in_port_t last_port,
char * proto,
sockaddr_union *svaddr,
int nonblock,
int priv,
char **stream_msg)
{
int s;
in_port_t port;
static in_port_t port_in_use[1024];
static int nb_port_in_use = 0;
int i;
int save_errno = EAGAIN;
assert(first_port <= last_port);
/* Try a port already used */
for(i=0; i < nb_port_in_use; i++) {
port = port_in_use[i];
if(port >= first_port && port <= last_port) {
s = connect_port(addrp, port, proto, svaddr, nonblock, priv, stream_msg);
if(s == -2) return -1;
if(s >= 0) {
return s;
}
if (errno != EAGAIN && errno != EBUSY)
save_errno = errno;
}
}
/* Try a port in the range */
for (port = first_port; port <= last_port; port++) {
s = connect_port(addrp, port, proto, svaddr, nonblock, priv, stream_msg);
if(s == -2) return -1;
if(s >= 0) {
port_in_use[nb_port_in_use++] = port;
return s;
}
if (errno != EAGAIN && errno != EBUSY)
save_errno = errno;
}
dbprintf(_("connect_portrange: All ports between %d and %d are busy.\n"),
first_port,
last_port);
errno = save_errno;
return -1;
}
static int
ambind(
int s,
sockaddr_union *addrp,
socklen_t_equiv socklen,
char **msg)
{
ambind_t ambind = {*addrp, socklen };
int sockfd[2];
int rc;
int pid;
struct msghdr msg_socket;
struct msghdr msg_ambind_data;
struct cmsghdr *cmsg;
char cmsgbuf[CMSG_SPACE(sizeof(s))];
struct iovec iov[2];
int r;
int pipe_stderr[2];
fd_set readSet;
int max_set;
#ifdef SOCK_NONBLOCK
r = socketpair(AF_UNIX, SOCK_DGRAM|SOCK_NONBLOCK, 0, sockfd);
#else
r = socketpair(AF_UNIX, SOCK_DGRAM, 0, sockfd);
if (r == 0) {
int r0 = fcntl(sockfd[0], F_GETFL, 0);
int r1 = fcntl(sockfd[1], F_GETFL, 0);
r0 = fcntl(sockfd[0], F_SETFL, r0|O_NONBLOCK);
r1 = fcntl(sockfd[1], F_SETFL, r1|O_NONBLOCK);
}
#endif
if (r < 0) {
*msg = g_strdup_printf("socketpair failed: %s\n", strerror(errno));
return -2;
}
if (pipe(pipe_stderr) < 0) {
shutdown(sockfd[0], SHUT_RDWR);
shutdown(sockfd[1], SHUT_RDWR);
*msg = g_strdup_printf("pipe failed: %s\n", strerror(errno));
return -2;
}
switch (pid = fork()) {
case -1: *msg = g_strdup_printf("fork ambind failed: %s",
strerror(errno));
close(sockfd[0]);
close(sockfd[1]);
close(pipe_stderr[0]);
close(pipe_stderr[1]);
return -2;
case 0: //child
{
char *ambind_path = g_strdup_printf("%s/ambind", amlibexecdir);
char *socket_name = g_strdup_printf("%d", sockfd[1]);
close(sockfd[0]);
dup2(pipe_stderr[1], 2);
safe_fd2(-1, 0, sockfd[1]);
execl(ambind_path, ambind_path, socket_name, NULL);
error("error [exec %s: %s]", ambind_path, strerror(errno));
exit(1);
}
default: //parent
close(sockfd[1]);
break;
}
close(pipe_stderr[1]);
memset(&msg_socket, 0, sizeof(msg_socket));
msg_socket.msg_control = cmsgbuf;
msg_socket.msg_controllen = sizeof(cmsgbuf); // necessary for CMSG_FIRSTHDR to return the correct value
cmsg = CMSG_FIRSTHDR(&msg_socket);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(s));
memcpy(CMSG_DATA(cmsg), &s, sizeof(s));
msg_socket.msg_controllen = cmsg->cmsg_len;
// send the socket
if ((sendmsg(sockfd[0], &msg_socket, 0)) < 0) {
*msg = g_strdup_printf("sendmsg failed A: %s\n",
strerror(errno));
shutdown(sockfd[0], SHUT_RDWR);
close(pipe_stderr[1]);
return -2;
}
memset(&msg_ambind_data, 0, sizeof(msg_ambind_data));
iov[0].iov_base = &ambind;
iov[0].iov_len = sizeof(ambind_t);
iov[1].iov_base = NULL;
iov[1].iov_len = 0;
msg_ambind_data.msg_iov = iov;
msg_ambind_data.msg_iovlen = 1;
// send ambind data
if ((sendmsg(sockfd[0], &msg_ambind_data, 0)) < 0) {
*msg = g_strdup_printf("sendmsg failed B: %s\n",
strerror(errno));
shutdown(sockfd[0], SHUT_RDWR);
close(pipe_stderr[0]);
return -2;
}
shutdown(sockfd[0], SHUT_WR);
max_set = sockfd[0];
if (max_set < pipe_stderr[0]) max_set = pipe_stderr[0];
max_set++;
do {
struct timeval timeout = { 5, 0 };
FD_ZERO(&readSet);
FD_SET(sockfd[0], &readSet);
FD_SET(pipe_stderr[0], &readSet);
rc = select(max_set, &readSet, NULL, NULL, &timeout);
} while (rc < 0 && errno == EINTR);
if (!FD_ISSET(sockfd[0], &readSet)) {
FILE *err;
shutdown(sockfd[0], SHUT_RDWR);
waitpid(pid, NULL, 0);
err = fdopen(pipe_stderr[0], "r");
*msg = agets(err);
fclose(err);
if (strncmp(*msg, "WARNING:", 8) == 0) {
return -1;
}
return -2;
}
close(pipe_stderr[0]);
// read the message socket msg
memset(&msg_socket, 0, sizeof(msg_socket));
msg_socket.msg_control = cmsgbuf;
msg_socket.msg_controllen = sizeof(cmsgbuf);
rc = recvmsg(sockfd[0], &msg_socket, 0);
if (rc == -1) {
*msg = g_strdup_printf("first recvmsg failed: %s", strerror(errno));
return -1;
}
cmsg = CMSG_FIRSTHDR(&msg_socket);
if (cmsg == NULL || cmsg -> cmsg_type != SCM_RIGHTS) {
*msg = g_strdup_printf("The first control structure contains no file descriptor.\n");
return -2;
}
memcpy(&s, CMSG_DATA(cmsg), sizeof(s));
shutdown(sockfd[0], SHUT_RDWR);
waitpid(pid, NULL, 0);
return s;
}
/* addrp is my address */
/* svaddr is the address of the remote machine */
/* return -2: Don't try again */
/* return -1: Try with another port */
/* return >0: this is the connected socket */
int
connect_port(
sockaddr_union *addrp,
in_port_t port,
char * proto,
sockaddr_union *svaddr,
int nonblock,
int priv,
char **msg)
{
int save_errno;
struct servent * result;
socklen_t_equiv len;
socklen_t_equiv socklen;
int s;
int r;
#ifdef HAVE_FUNC_GETSERVBYNAME_R_6
struct servent servPort;
char buf[2048];
r = getservbyport_r((int)htons(port), proto, &servPort, buf, 2048, &result);
assert(r != ERANGE);
#elif defined HAVE_FUNC_GETSERVBYNAME_R_5
struct servent servPort;
char buf[2048];
result = getservbyport_r((int)htons(port), proto, &servPort, buf, 2048);
if (result == 0) {
assert(errno != ERANGE);
}
#elif defined HAVE_FUNC_GETSERVBYNAME_R_4
struct servent servPort;
struct servent_data servent_data;
memset(&servent_data, 0, sizeof(struct servent_data));
r = getservbyport_r((int)htons(port), proto, &servPort, &servent_data);
result = &servPort;
#else
result = getservbyport((int)htons(port), proto);
#endif
if (result != NULL && !strstr(result->s_name, AMANDA_SERVICE_NAME)) {
dbprintf(_("connect_port: Skip port %d: owned by %s.\n"),
port, result->s_name);
errno = EBUSY;
return -1;
}
if ((s = make_socket(SU_GET_FAMILY(addrp))) == -1) return -2;
SU_SET_PORT(addrp, port);
socklen = SS_LEN(addrp);
if (!priv) {
r = bind(s, (struct sockaddr *)addrp, socklen);
#if !defined BROKEN_SENDMSG
} else if (1) { // if use ambind
int old_s = s;
amfree(*msg);
r = s = ambind(s, addrp, socklen, msg);
close(old_s);
if (*msg) {
g_debug("ambind failed: %s", *msg);
}
if (r == -2) {
return -2;
}
#endif
} else { // setuid root
g_mutex_lock(priv_mutex);
set_root_privs(1);
r = bind(s, (struct sockaddr *)addrp, socklen);
set_root_privs(0);
g_mutex_unlock(priv_mutex);
}
if (r < 0) {
save_errno = errno;
aclose(s);
if( result == NULL) {
dbprintf(_("connect_port: Try port %d: available - %s\n"),
port, strerror(save_errno));
} else {
dbprintf(_("connect_port: Try port %d: owned by %s - %s\n"),
port, result->s_name, strerror(save_errno));
}
if (save_errno != EADDRINUSE) {
errno = save_errno;
return -2;
}
errno = save_errno;
return -1;
}
if (result == NULL) {
dbprintf(_("connect_port: Try port %d: available - Success\n"), port);
} else {
dbprintf(_("connect_port: Try port %d: owned by %s - Success\n"),
port, result->s_name);
}
/* find out what port was actually used */
len = sizeof(*addrp);
if (getsockname(s, (struct sockaddr *)addrp, &len) == -1) {
save_errno = errno;
dbprintf(_("connect_port: getsockname() failed: %s\n"),
strerror(save_errno));
aclose(s);
errno = save_errno;
return -1;
}
if (nonblock) {
int r = fcntl(s, F_GETFL, 0);
if (r < 0) {
save_errno = errno;
g_debug("Can't fcntl(F_GETFL): %s", strerror(errno));
aclose(s);
errno = save_errno;
return -1;
}
r = fcntl(s, F_SETFL, r|O_NONBLOCK);
if (r < 0) {
save_errno = errno;
g_debug("Can't fcntl(F_SETFL,O_NONBLOCK): %s", strerror(errno));
errno = save_errno;
aclose(s);
return -1;
}
}
if (connect(s, (struct sockaddr *)svaddr, SS_LEN(svaddr)) == -1 && !nonblock) {
save_errno = errno;
dbprintf(_("connect_portrange: Connect from %s failed: %s\n"),
str_sockaddr(addrp),
strerror(save_errno));
dbprintf(_("connect_portrange: connect to %s failed: %s\n"),
str_sockaddr(svaddr),
strerror(save_errno));
aclose(s);
errno = save_errno;
if (save_errno == ECONNREFUSED ||
save_errno == EHOSTUNREACH ||
save_errno == ENETUNREACH ||
save_errno == ETIMEDOUT) {
return -2;
}
return -1;
}
dbprintf(_("connected to %s\n"),
str_sockaddr(svaddr));
dbprintf(_("our side is %s\n"),
str_sockaddr(addrp));
return s;
}
/*
* Bind to a port in the given range. Takes a begin,end pair of port numbers.
*
* Returns negative on error (EGAIN if all ports are in use).
* Returns the new socket on success
*/
int
bind_portrange(
int s,
sockaddr_union *addrp,
in_port_t first_port,
in_port_t last_port,
char * proto,
int priv,
char **bind_msg)
{
in_port_t port;
in_port_t cnt;
socklen_t_equiv socklen;
struct servent *result;
const in_port_t num_ports = (in_port_t)(last_port - first_port + 1);
int save_errno = EAGAIN;
int new_s;
int r;
assert(first_port <= last_port);
/*
* We pick a different starting port based on our pid and the current
* time to avoid always picking the same reserved port twice.
*/
port = (in_port_t)(((getpid() + time(0)) % num_ports) + first_port);
/*
* Scan through the range, trying all available ports that are either
* not taken in /etc/services or registered for *amanda*. Wrap around
* if we don't happen to start at the beginning.
*/
for (cnt = 0; cnt < num_ports; cnt++) {
#ifdef HAVE_FUNC_GETSERVBYNAME_R_6
struct servent servPort;
char buf[2048];
r = getservbyport_r((int)htons(port), proto, &servPort, buf, 2048, &result);
assert(r != ERANGE);
#elif defined HAVE_FUNC_GETSERVBYNAME_R_5
struct servent servPort;
char buf[2048];
result = getservbyport_r((int)htons(port), proto, &servPort, buf, 2048);
if (result == 0) {
assert(errno != ERANGE);
}
#elif defined HAVE_FUNC_GETSERVBYNAME_R_4
struct servent servPort;
struct servent_data servent_data;
memset(&servent_data, 0, sizeof(struct servent_data));
r = getservbyport_r((int)htons(port), proto, &servPort, &servent_data);
result = &servPort
#else
result = getservbyport((int)htons(port), proto);
#endif
amfree(*bind_msg);
g_debug("bind_portrange2: Try port %d", port);
if ((result == NULL) || strstr(result->s_name, AMANDA_SERVICE_NAME)) {
SU_SET_PORT(addrp, port);
socklen = SS_LEN(addrp);
if (!priv) {
r = bind(s, (struct sockaddr *)addrp, socklen);
new_s = s;
*bind_msg = g_strdup(strerror(errno));
#if !defined BROKEN_SENDMSG
} else if (1) { // if use ambind
r = new_s = ambind(s, addrp, socklen, bind_msg);
if (*bind_msg) {
g_debug("ambind failed: %s", *bind_msg);
}
if (r == -2) {
amfree(*bind_msg);
return -1;
}
#endif
} else {
g_mutex_lock(priv_mutex);
set_root_privs(1);
r = bind(s, (struct sockaddr *)addrp, socklen);
new_s = s;
*bind_msg = g_strdup(strerror(errno));
set_root_privs(0);
g_mutex_unlock(priv_mutex);
}
if (r >= 0) {
if (result == NULL) {
g_debug(_("bind_portrange2: Try port %d: Available - Success"), port);
} else {
g_debug(_("bind_portrange2: Try port %d: Owned by %s - Success."), port, result->s_name);
}
return new_s;
}
if (errno != EAGAIN && errno != EBUSY)
save_errno = errno;
if (result == NULL) {
g_debug(_("bind_portrange2: Try port %d: Available - %s"),
port, *bind_msg);
} else {
g_debug(_("bind_portrange2: Try port %d: Owned by %s - %s"),
port, result->s_name, *bind_msg);
}
} else {
g_debug(_("bind_portrange2: Skip port %d: Owned by %s."),
port, result->s_name);
}
if (++port > last_port)
port = first_port;
}
g_debug(_("bind_portrange: all ports between %d and %d busy"),
first_port,
last_port);
errno = save_errno;
return -2;
}
int
interruptible_accept(
int sock,
struct sockaddr *addr,
socklen_t *addrlen,
gboolean (*prolong)(gpointer data),
gpointer prolong_data,
time_t timeout)
{
SELECT_ARG_TYPE readset;
struct timeval tv;
int nfound;
if (sock < 0 || sock >= FD_SETSIZE) {
g_debug("interruptible_accept: bad socket %d", sock);
return EBADF;
}
memset(&readset, 0, sizeof(readset));
while (1) {
if (prolong && !prolong(prolong_data)) {
errno = 0;
return -1;
}
if (time(NULL) > timeout) {
errno = ETIMEDOUT;
return -1;
}
FD_ZERO(&readset);
FD_SET(sock, &readset);
/* try accepting for 1s */
memset(&tv, 0, sizeof(tv));
tv.tv_sec = 1;
nfound = select(sock+1, &readset, NULL, NULL, &tv);
if (nfound < 0) {
return -1;
} else if (nfound == 0) {
continue;
} else if (!FD_ISSET(sock, &readset)) {
g_debug("interruptible_accept: select malfunction");
errno = EBADF;
return -1;
} else {
int rv = accept(sock, addr, addrlen);
if (rv < 0 && errno == EAGAIN)
continue;
return rv;
}
}
}
/*
* Writes out the entire iovec
*/
ssize_t
full_writev(
int fd,
struct iovec * iov,
int iovcnt)
{
ssize_t delta, n, total;
assert(iov != NULL);
total = 0;
while (iovcnt > 0) {
/*
* Write the iovec
*/
n = writev(fd, iov, iovcnt);
if (n < 0) {
if (errno != EINTR)
return (-1);
}
else if (n == 0) {
errno = EIO;
return (-1);
} else {
total += n;
/*
* Iterate through each iov. Figure out what we still need
* to write out.
*/
for (; n > 0; iovcnt--, iov++) {
/* 'delta' is the bytes written from this iovec */
delta = ((size_t)n < (size_t)iov->iov_len) ? n : (ssize_t)iov->iov_len;
/* subtract from the total num bytes written */
n -= delta;
assert(n >= 0);
/* subtract from this iovec */
iov->iov_len -= delta;
iov->iov_base = (char *)iov->iov_base + delta;
/* if this iovec isn't empty, run the writev again */
if (iov->iov_len > 0)
break;
}
}
}
return (total);
}
/*
* For backward compatibility we are trying for minimal quoting. Unless ALWAYS
* is true, we only quote a string if it contains whitespace or is misquoted...
*/
char *
quote_string_maybe(
const char *str,
gboolean always)
{
char * s;
char * ret;
if ((str == NULL) || (*str == '\0')) {
ret = g_strdup("\"\"");
} else {
const char *r;
for (r = str; *r; r++) {
if (*r == ':' || *r == '\'' || *r == '\\' || *r == '\"' ||
*r <= ' ' || *r == 0x7F )
always = 1;
}
if (!always) {
/*
* String does not need to be quoted since it contains
* neither whitespace, control or quote characters.
*/
ret = g_strdup(str);
} else {
/*
* Allocate maximum possible string length.
* (a string of all quotes plus room for leading ", trailing " and
* NULL)
*/
ret = s = g_malloc((strlen(str) * 2) + 2 + 1);
*(s++) = '"';
while (*str != '\0') {
if (*str == '\t') {
*(s++) = '\\';
*(s++) = 't';
str++;
continue;
} else if (*str == '\n') {
*(s++) = '\\';
*(s++) = 'n';
str++;
continue;
} else if (*str == '\r') {
*(s++) = '\\';
*(s++) = 'r';
str++;
continue;
} else if (*str == '\f') {
*(s++) = '\\';
*(s++) = 'f';
str++;
continue;
} else if (*str == '\\') {
*(s++) = '\\';
*(s++) = '\\';
str++;
continue;
}
if (*str == '"')
*(s++) = '\\';
*(s++) = *(str++);
}
*(s++) = '"';
*s = '\0';
}
}
return (ret);
}
int
len_quote_string_maybe(
const char *str,
gboolean always)
{
int ret;
if ((str == NULL) || (*str == '\0')) {
ret = 0;
} else {
const char *r;
for (r = str; *r; r++) {
if (*r == ':' || *r == '\'' || *r == '\\' || *r == '\"' ||
*r <= ' ' || *r == 0x7F )
always = 1;
}
if (!always) {
/*
* String does not need to be quoted since it contains
* neither whitespace, control or quote characters.
*/
ret = strlen(str);
} else {
/*
* Allocate maximum possible string length.
* (a string of all quotes plus room for leading ", trailing " and
* NULL)
*/
ret = 1;
while (*str != '\0') {
if (*str == '\t') {
ret++;
ret++;
str++;
continue;
} else if (*str == '\n') {
ret++;
ret++;
str++;
continue;
} else if (*str == '\r') {
ret++;
ret++;
str++;
continue;
} else if (*str == '\f') {
ret++;
ret++;
str++;
continue;
} else if (*str == '\\') {
ret++;
ret++;
str++;
continue;
}
if (*str == '"')
ret++;
ret++;
str++;
}
ret++;
}
}
return (ret);
}
char *
unquote_string(
const char *str)
{
char * ret;
if ((str == NULL) || (*str == '\0')) {
ret = g_strdup("");
} else {
char * in;
char * out;
ret = in = out = g_strdup(str);
while (*in != '\0') {
if (*in == '"') {
in++;
continue;
}
if (*in == '\\') {
in++;
if (*in == 'n') {
in++;
*(out++) = '\n';
continue;
} else if (*in == 't') {
in++;
*(out++) = '\t';
continue;
} else if (*in == 'r') {
in++;
*(out++) = '\r';
continue;
} else if (*in == 'f') {
in++;
*(out++) = '\f';
continue;
} else if (*in >= '0' && *in <= '7') {
char c = 0;
int i = 0;
while (i < 3 && *in >= '0' && *in <= '7') {
c = (c << 3) + *(in++) - '0';
i++;
}
if (c)
*(out++) = c;
} else if (*in == '\0') {
/* trailing backslash -- ignore */
break;
}
}
*(out++) = *(in++);
}
*out = '\0';
}
return (ret);
}
gchar **
split_quoted_strings(
const gchar *string)
{
char *local;
char *start;
char *p;
char **result;
GPtrArray *strs;
int iq = 0;
if (!string)
return NULL;
p = start = local = g_strdup(string);
strs = g_ptr_array_new();
while (*p) {
if (!iq && *p == ' ') {
*p = '\0';
g_ptr_array_add(strs, unquote_string(start));
start = p+1;
} else if (*p == '\\') {
/* next character is taken literally; if it's a multicharacter
* escape (e.g., \171), that doesn't bother us here */
p++;
if (!*p) break;
} else if (*p == '\"') {
iq = ! iq;
}
p++;
}
if (start != string)
g_ptr_array_add(strs, unquote_string(start));
/* now convert strs into a strv, by stealing its references to the underlying
* strings */
result = g_new0(char *, strs->len + 1);
memmove(result, strs->pdata, sizeof(char *) * strs->len);
g_ptr_array_free(strs, TRUE); /* TRUE => free pdata, strings are not freed */
g_free(local);
return result;
}
gchar **
split_quoted_strings_for_amstatus(
const gchar *string)
{
char *local;
char *start;
char *p;
char **result;
GPtrArray *strs;
int iq = 0;
if (!string)
return NULL;
p = start = local = g_strdup(string);
strs = g_ptr_array_new();
while (*p) {
if (!iq && (*p == ' ' || *p == ':')) {
*p = '\0';
if (start != p) {
g_ptr_array_add(strs, unquote_string(start));
}
start = p+1;
} else if (*p == '\\') {
/* next character is taken literally; if it's a multicharacter
* escape (e.g., \171), that doesn't bother us here */
p++;
if (!*p) break;
} else if (*p == '\"') {
iq = ! iq;
}
p++;
}
if (start != string)
g_ptr_array_add(strs, unquote_string(start));
/* now convert strs into a strv, by stealing its references to the underlying
* strings */
result = g_new0(char *, strs->len + 1);
memmove(result, strs->pdata, sizeof(char *) * strs->len);
g_ptr_array_free(strs, TRUE); /* TRUE => free pdata, strings are not freed */
g_free(local);
return result;
}
char *
strquotedstr(char **saveptr)
{
char * tok = strtok_r(NULL, " ", saveptr);
size_t len;
int in_quote;
int in_backslash;
char *p, *t;
if (!tok)
return tok;
len = strlen(tok);
in_quote = 0;
in_backslash = 0;
p = tok;
while (in_quote || in_backslash || *p != '\0') {
if (*p == '\0') {
/* append a new token */
t = strtok_r(NULL, " ", saveptr);
if (!t)
return NULL;
tok[len] = ' ';
len = strlen(tok);
}
if (!in_backslash) {
if (*p == '"')
in_quote = !in_quote;
else if (*p == '\\') {
in_backslash = 1;
}
} else {
in_backslash = 0;
}
p++;
}
return tok;
}
char *
sanitize_string(
const char *str)
{
char * s;
char * ret;
if ((str == NULL) || (*str == '\0')) {
ret = g_strdup("");
} else {
ret = g_strdup(str);
for (s = ret; *s != '\0'; s++) {
if (iscntrl((int)*s))
*s = '?';
}
}
return (ret);
}
char *hexencode_string(const char *str)
{
size_t orig_len, new_len, i;
GString *s;
gchar *ret;
if (!str) {
s = g_string_sized_new(0);
goto cleanup;
}
new_len = orig_len = strlen(str);
for (i = 0; i < orig_len; i++) {
if (!g_ascii_isalnum(str[i])) {
new_len += 2;
}
}
s = g_string_sized_new(new_len);
for (i = 0; i < orig_len; i++) {
if (g_ascii_isalnum(str[i])) {
g_string_append_c(s, str[i]);
} else {
g_string_append_printf(s, "%%%02hhx", str[i]);
}
}
cleanup:
ret = s->str;
g_string_free(s, FALSE);
return ret;
}
char *hexdecode_string(const char *str, GError **err)
{
size_t orig_len, new_len, i;
GString *s;
gchar *ret;
if (!str) {
s = g_string_sized_new(0);
goto cleanup;
}
new_len = orig_len = strlen(str);
for (i = 0; i < orig_len; i++) {
if (str[i] == '%') {
new_len -= 2;
}
}
s = g_string_sized_new(new_len);
for (i = 0; (orig_len > 2) && (i < orig_len-2); i++) {
if (str[i] == '%') {
gchar tmp = 0;
size_t j;
for (j = 1; j < 3; j++) {
tmp <<= 4;
if (str[i+j] >= '0' && str[i+j] <= '9') {
tmp += str[i+j] - '0';
} else if (str[i+j] >= 'a' && str[i+j] <= 'f') {
tmp += str[i+j] - 'a' + 10;
} else if (str[i+j] >= 'A' && str[i+j] <= 'F') {
tmp += str[i+j] - 'A' + 10;
} else {
/* error */
g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
"Illegal character (non-hex) 0x%02hhx at offset %zd", str[i+j], i+j);
g_string_truncate(s, 0);
goto cleanup;
}
}
if (!tmp) {
g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
"Encoded NULL at starting offset %zd", i);
g_string_truncate(s, 0);
goto cleanup;
}
g_string_append_c(s, tmp);
i += 2;
} else {
g_string_append_c(s, str[i]);
}
}
for ( /*nothing*/; i < orig_len; i++) {
if (str[i] == '%') {
g_set_error(err, am_util_error_quark(), AM_UTIL_ERROR_HEXDECODEINVAL,
"'%%' found at offset %zd, but fewer than two characters follow it (%zd)", i, orig_len-i-1);
g_string_truncate(s, 0);
goto cleanup;
} else {
g_string_append_c(s, str[i]);
}
}
cleanup:
ret = s->str;
g_string_free(s, FALSE);
return ret;
}
/* Helper for parse_braced_component; this will turn a single element array
* matching /^\d+\.\.\d+$/ into a sequence of numbered array elements. */
static GPtrArray *
expand_braced_sequence(GPtrArray *arr)
{
char *elt, *p;
char *l, *r;
int ldigits, rdigits, ndigits;
guint64 start, end;
gboolean leading_zero;
/* check whether the element matches the pattern */
/* expand last element of the array only */
elt = g_ptr_array_index(arr, arr->len-1);
ldigits = 0;
for (l = p = elt; *p && g_ascii_isdigit(*p); p++)
ldigits++;
if (ldigits == 0)
return arr;
if (*(p++) != '.')
return arr;
if (*(p++) != '.')
return arr;
rdigits = 0;
for (r = p; *p && g_ascii_isdigit(*p); p++)
rdigits++;
if (rdigits == 0)
return arr;
if (*p)
return arr;
/* we have a match, so extract start and end */
start = g_ascii_strtoull(l, NULL, 10);
end = g_ascii_strtoull(r, NULL, 10);
leading_zero = *l == '0';
ndigits = MAX(ldigits, rdigits);
if (start > end)
return arr;
/* sanity check.. */
if (end - start > 100000)
return arr;
/* remove last from the array */
g_ptr_array_remove_index(arr, arr->len - 1);
/* Add new elements */
while (start <= end) {
if (leading_zero) {
g_ptr_array_add(arr, g_strdup_printf("%0*ju",
ndigits, (uintmax_t)start));
} else {
g_ptr_array_add(arr, g_strdup_printf("%ju", (uintmax_t)start));
}
start++;
}
g_free(elt);
return arr;
}
/* Helper for expand_braced_alternates; returns a list of un-escaped strings
* for the first "component" of str, where a component is a plain string or a
* brace-enclosed set of alternatives. str is pointing to the first character
* of the next component on return. */
static GPtrArray *
parse_braced_component(char **str)
{
GPtrArray *result = g_ptr_array_new();
if (**str == '{') {
char *p = (*str)+1;
char *local = g_malloc(strlen(*str)+1);
char *current = local;
char *c = current;
while (1) {
if (*p == '\0' || *p == '{') {
/* unterminated { .. } or extra '{' */
amfree(local);
g_ptr_array_free(result, TRUE);
return NULL;
}
if (*p == '}' || *p == ',') {
*c = '\0';
g_ptr_array_add(result, g_strdup(current));
result = expand_braced_sequence(result);
current = ++c;
if (*p == '}')
break;
else
p++;
}
if (*p == '\\') {
if (*(p+1) == '{' || *(p+1) == '}' || *(p+1) == '\\' || *(p+1) == ',')
p++;
}
*(c++) = *(p++);
}
amfree(local);
if (*p)
*str = p+1;
else
*str = p;
} else {
/* no braces -- just un-escape a plain string */
char *local = g_malloc(strlen(*str)+1);
char *r = local;
char *p = *str;
while (*p && *p != '{') {
if (*p == '\\') {
if (*(p+1) == '{' || *(p+1) == '}' || *(p+1) == '\\' || *(p+1) == ',')
p++;
}
*(r++) = *(p++);
}
*r = '\0';
g_ptr_array_add(result, local);
*str = p;
}
return result;
}
GPtrArray *
expand_braced_alternates(
char * source)
{
GPtrArray *rval = g_ptr_array_new();
gpointer *pdata;
g_ptr_array_add(rval, g_strdup(""));
while (*source) {
GPtrArray *new_components;
GPtrArray *new_rval;
guint i, j;
new_components = parse_braced_component(&source);
if (!new_components) {
/* parse error */
for (i = 0, pdata = rval->pdata; i < rval->len; i++)
g_free(*pdata++);
g_ptr_array_free(rval, TRUE);
return NULL;
}
new_rval = g_ptr_array_new();
/* do a cartesian join of rval and new_components */
for (i = 0; i < rval->len; i++) {
for (j = 0; j < new_components->len; j++) {
g_ptr_array_add(new_rval, g_strconcat(
g_ptr_array_index(rval, i),
g_ptr_array_index(new_components, j),
NULL));
}
}
for (i = 0, pdata = rval->pdata; i < rval->len; i++)
g_free(*pdata++);
g_ptr_array_free(rval, TRUE);
for (i = 0, pdata = new_components->pdata; i < new_components->len; i++)
g_free(*pdata++);
g_ptr_array_free(new_components, TRUE);
rval = new_rval;
}
return rval;
}
char *
collapse_braced_alternates(
GPtrArray *source)
{
GString *result = NULL;
guint i;
result = g_string_new("{");
for (i = 0; i < source->len; i ++) {
const char *str = g_ptr_array_index(source, i);
char *qstr = NULL;
if (strchr(str, ',') || strchr(str, '\\') ||
strchr(str, '{') || strchr(str, '}')) {
const char *s;
char *d;
s = str;
qstr = d = g_malloc(strlen(str)*2+1);
while (*s) {
if (*s == ',' || *s == '\\' || *s == '{' || *s == '}')
*(d++) = '\\';
*(d++) = *(s++);
}
*(d++) = '\0';
}
g_string_append_printf(result, "%s%s", qstr? qstr : str,
(i < source->len-1)? "," : "");
if (qstr)
g_free(qstr);
}
g_string_append(result, "}");
return g_string_free(result, FALSE);
}
/*
Return 0 if the following characters are present
* ( ) < > [ ] , ; : ! $ \ / "
else returns 1
*/
int
validate_mailto(
const char *mailto)
{
return !match("\\*|<|>|\\(|\\)|\\[|\\]|,|;|:|\\\\|/|\"|\\!|\\$|\\|", mailto);
}
int copy_file(
char *dst,
char *src,
char **errmsg)
{
int infd, outfd;
int save_errno;
ssize_t nb;
char buf[32768];
char *quoted;
if ((infd = open(src, O_RDONLY)) == -1) {
save_errno = errno;
quoted = quote_string(src);
*errmsg = g_strdup_printf(_("Can't open file '%s' for reading: %s"),
quoted, strerror(save_errno));
amfree(quoted);
return -1;
}
if ((outfd = open(dst, O_WRONLY|O_CREAT, 0600)) == -1) {
save_errno = errno;
quoted = quote_string(dst);
*errmsg = g_strdup_printf(_("Can't open file '%s' for writing: %s"),
quoted, strerror(save_errno));
amfree(quoted);
close(infd);
return -1;
}
while ((nb=read(infd, &buf, sizeof(buf))) > 0) {
if (full_write(outfd,&buf,nb) < (size_t)nb) {
save_errno = errno;
quoted = quote_string(dst);
*errmsg = g_strdup_printf(_("Error writing to '%s': %s"),
quoted, strerror(save_errno));
amfree(quoted);
close(infd);
close(outfd);
return -1;
}
}
if (errno != 0) {
save_errno = errno;
quoted = quote_string(src);
*errmsg = g_strdup_printf(_("Error reading from '%s': %s"),
quoted, strerror(save_errno));
amfree(quoted);
close(infd);
close(outfd);
return -1;
}
close(infd);
close(outfd);
return 0;
}
#ifndef HAVE_LIBREADLINE
/*
* simple readline() replacements, used when we don't have readline
* support from the system.
*/
char *
readline(
const char *prompt)
{
g_printf("%s", prompt);
fflush(stdout);
fflush(stderr);
return pgets(stdin);
}
void
add_history(
const char *line)
{
(void)line; /* Quiet unused parameter warning */
}
#endif
/* Order of preference: readdir64(), readdir(). */
#if HAVE_DECL_READDIR64
# define USE_DIRENT64
# define USE_READDIR64
#elif HAVE_DECL_READDIR
# define USE_READDIR
#else
# error No readdir() or readdir64() available!
#endif
#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
char * portable_readdir(DIR* handle) {
#ifdef USE_DIRENT64
struct dirent64 *entry_p;
#else
struct dirent *entry_p;
#endif
static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
g_static_mutex_lock(&mutex);
#ifdef USE_READDIR
entry_p = readdir(handle);
#endif
#ifdef USE_READDIR64
entry_p = readdir64(handle);
#endif
g_static_mutex_unlock(&mutex);
if (entry_p == NULL)
return NULL;
/* FIXME: According to glibc documentation, d_name may not be
null-terminated in some cases on some very old platforms. Not
sure what to do about that case. */
return strdup(entry_p->d_name);
}
#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic pop
#endif
int search_directory(DIR * handle, const char * regex,
SearchDirectoryFunctor functor, gpointer user_data) {
int rval = 0;
regex_t compiled_regex;
gboolean done = FALSE;
if (regcomp(&compiled_regex, regex, REG_EXTENDED | REG_NOSUB) != 0) {
regfree(&compiled_regex);
return -1;
}
rewinddir(handle);
while (!done) {
char * read_name;
int result;
read_name = portable_readdir(handle);
if (read_name == NULL) {
regfree(&compiled_regex);
return rval;
}
result = regexec(&compiled_regex, read_name, 0, NULL, 0);
if (result == 0) {
rval ++;
done = !functor(read_name, user_data);
}
amfree(read_name);
}
regfree(&compiled_regex);
return rval;
}
char* find_regex_substring(const char* base_string, const regmatch_t match) {
char * rval;
int size;
size = match.rm_eo - match.rm_so;
rval = malloc(size+1);
memcpy(rval, base_string + match.rm_so, size);
rval[size] = '\0';
return rval;
}
int compare_possibly_null_strings(const char * a, const char * b) {
if (a == b) {
/* NULL or otherwise, they're the same. */
return 0;
} else if (a == NULL) {
/* b != NULL */
return -1;
} else if (b == NULL) {
/* a != NULL */
return 1;
} else {
/* a != NULL != b */
return strcmp(a, b);
}
}
int
resolve_hostname(const char *hostname,
int socktype,
struct addrinfo **res,
char **canonname)
{
struct addrinfo hints;
struct addrinfo *myres;
int flags = 0;
int result;
if (res) *res = NULL;
if (canonname) {
*canonname = NULL;
flags = AI_CANONNAME;
}
#ifdef AI_ADDRCONFIG
flags |= AI_ADDRCONFIG;
#endif
memset(&hints, 0, sizeof(hints));
#ifdef WORKING_IPV6
/* get any kind of addresss */
hints.ai_family = AF_UNSPEC;
#else
/* even if getaddrinfo supports IPv6, don't let it return
* such an address */
hints.ai_family = AF_INET;
#endif
hints.ai_flags = flags;
hints.ai_socktype = socktype;
result = getaddrinfo(hostname, NULL, &hints, &myres);
if (result != 0) {
return result;
}
if (canonname && myres && myres->ai_canonname) {
*canonname = g_strdup(myres->ai_canonname);
}
if (res) {
*res = myres;
} else {
freeaddrinfo(myres);
}
return result;
}
char *
_str_exit_status(
char *subject,
amwait_t status)
{
if (WIFEXITED(status)) {
int exitstatus = WEXITSTATUS(status);
if (exitstatus == 0)
return g_strdup_printf(_("%s exited normally"), subject);
else
return g_strdup_printf(_("%s exited with status %d"), subject, exitstatus);
}
if (WIFSIGNALED(status)) {
int signal = WTERMSIG(status);
#ifdef WCOREDUMP
if (WCOREDUMP(status))
return g_strdup_printf(_("%s exited after receiving signal %d (core dumped)"),
subject, signal);
else
#endif
return g_strdup_printf(_("%s exited after receiving signal %d"),
subject, signal);
}
if (WIFSTOPPED(status)) {
int signal = WSTOPSIG(status);
return g_strdup_printf(_("%s stopped temporarily after receiving signal %d"),
subject, signal);
}
#ifdef WIFCONTINUED
if (WIFCONTINUED(status)) {
return g_strdup_printf(_("%s was resumed"), subject);
}
#endif
return g_strdup_printf(_("%s exited in unknown circumstances"), subject);
}
void
check_running_as(running_as_flags who)
{
#ifdef CHECK_USERID
struct passwd *pw;
uid_t uid_me;
uid_t uid_target;
char *uname_me = NULL;
char *uname_target = NULL;
char *dumpuser;
uid_me = getuid();
if ((pw = getpwuid(uid_me)) == NULL) {
error(_("current userid %ld not found in password database"), (long)uid_me);
/* NOTREACHED */
}
uname_me = g_strdup(pw->pw_name);
#ifndef SINGLE_USERID
if (!(who & RUNNING_AS_UID_ONLY) && uid_me != geteuid()) {
error(_("euid (%lld) does not match uid (%lld); is this program setuid-root when it shouldn't be?"),
(long long int)geteuid(), (long long int)uid_me);
/* NOTREACHED */
}
#endif
switch (who & RUNNING_AS_USER_MASK) {
case RUNNING_AS_ANY:
uid_target = uid_me;
uname_target = uname_me;
amfree(uname_me);
return;
case RUNNING_AS_ROOT:
uid_target = 0;
uname_target = "root";
break;
case RUNNING_AS_DUMPUSER_PREFERRED:
dumpuser = getconf_str(CNF_DUMPUSER);
if ((pw = getpwnam(dumpuser)) != NULL &&
uid_me != pw->pw_uid) {
if ((pw = getpwnam(CLIENT_LOGIN)) != NULL &&
uid_me == pw->pw_uid) {
/* uid == CLIENT_LOGIN: not ideal, but OK */
dbprintf(_("NOTE: running as '%s', which is the client"
" user, not the dumpuser ('%s'); forging"
" on anyway\n"),
CLIENT_LOGIN, dumpuser);
uid_target = uid_me; /* force success below */
break;
}
}
/* FALLTHROUGH */
case RUNNING_AS_DUMPUSER:
uname_target = getconf_str(CNF_DUMPUSER);
if ((pw = getpwnam(uname_target)) == NULL) {
error(_("cannot look up dumpuser \"%s\""), uname_target);
/*NOTREACHED*/
}
uid_target = pw->pw_uid;
break;
case RUNNING_AS_CLIENT_LOGIN:
uname_target = CLIENT_LOGIN;
if ((pw = getpwnam(uname_target)) == NULL) {
error(_("cannot look up client user \"%s\""), uname_target);
/*NOTREACHED*/
}
uid_target = pw->pw_uid;
break;
default:
error(_("Unknown check_running_as() call"));
/* NOTREACHED */
}
if (uid_me != uid_target) {
error("must be executed as the \"%s\" user instead of the \"%s\" user", uname_target, uname_me);
/*NOTREACHED*/
}
amfree(uname_me);
#else
/* Quiet unused variable warning */
(void)who;
#endif
}
int
set_root_privs(int need_root)
{
#ifndef SINGLE_USERID
static gboolean first_call = TRUE;
static uid_t unpriv = 1;
if (first_call) {
/* save the original real userid (that of our invoker) */
unpriv = getuid();
/* and set all of our userids (including, importantly, the saved
* userid) to 0
* It can fail we are not 0, we don't care */
setuid(0);
/* don't need to do this next time */
first_call = FALSE;
}
if (need_root == 1) {
if (geteuid() == 0) return 1; /* already done */
if (seteuid(0) == -1) return 0;
/* (we don't switch the group back) */
} else if (need_root == -1) {
/* make sure the euid is 0 so that we can set the uid */
if (geteuid() != 0) {
if (seteuid(0) == -1) return 0;
}
/* now set the uid to the unprivileged userid */
if (setuid(unpriv) == -1) return 0;
} else {
if (geteuid() != 0) return 1; /* already done */
/* set the *effective* userid only */
if (seteuid(unpriv) == -1) return 0;
if (setegid(getgid()) == -1) return 0;
}
#else
(void)need_root; /* Quiet unused variable warning */
#endif
return 1;
}
int
become_root(void)
{
#ifndef SINGLE_USERID
/* first, set the effective userid to 0 */
if (seteuid(0) == -1) return 0;
/* then, set all of the userids to 0 */
if (setuid(0) == -1) return 0;
#endif
return 1;
}
char *
base64_decode_alloc_string(
char *in)
{
char *out;
size_t in_len = strlen(in);
size_t out_len = 3 * (in_len / 4) + 3;
out = malloc(out_len);
if (!base64_decode(in, in_len, out, &out_len)) {
amfree(out);
return NULL;
}
out[out_len] = '\0';
return out;
}
/* A GHFunc (callback for g_hash_table_foreach),
* Store a property and it's value in an ARGV.
*
* @param key_p: (char *) property name.
* @param value_p: (GSList *) property values list.
* @param user_data_p: (char ***) pointer to ARGV.
*/
static void
proplist_add_to_argv(
gpointer key_p,
gpointer value_p,
gpointer user_data_p)
{
char *property_s = key_p;
property_t *value_s = value_p;
GPtrArray *argv_ptr = user_data_p;
GSList *value;
char *q, *w, *qprop;
q = g_strdup(property_s);
/* convert to lower case */
for (w=q; *w != '\0'; w++) {
*w = tolower(*w);
if (*w == '_')
*w = '-';
}
qprop = g_strconcat("--", q, NULL);
amfree(q);
for(value=value_s->values; value != NULL; value = value->next) {
g_ptr_array_add(argv_ptr, g_strdup(qprop));
g_ptr_array_add(argv_ptr, g_strdup((char *)value->data));
}
amfree(qprop);
}
void
property_add_to_argv(
GPtrArray *argv_ptr,
GHashTable *proplist)
{
g_hash_table_foreach(proplist, &proplist_add_to_argv, argv_ptr);
}
/*
* Process parameters
*/
static char *pname = NULL;
static char *ptype = NULL;
static pcontext_t pcontext = CONTEXT_DEFAULT;
static char *pcomponent = NULL;
static char *pmodule = NULL;
static GQueue *component_stack;
static GQueue *module_stack;
void
set_pname(char *p)
{
g_free(pname);
pname = g_strdup(p);
}
char *
get_pname(void)
{
if (!pname) pname = g_strdup("unknown");
return pname;
}
void
set_ptype(char *p)
{
g_free(ptype);
ptype = g_strdup(p);
}
char *
get_ptype(void)
{
if (!ptype) ptype = g_strdup("unknown");
return ptype;
}
void
set_pcontext(pcontext_t pc)
{
pcontext = pc;
}
pcontext_t
get_pcontext(void)
{
return pcontext;
}
void
set_pcomponent(char *component)
{
g_free(pcomponent);
pcomponent = g_strdup(component);
}
char *
get_pcomponent(void)
{
if (!pcomponent) pcomponent = g_strdup("amanda");
return pcomponent;
}
void
set_pmodule(char *module)
{
g_free(pmodule);
pmodule = g_strdup(module);
}
char *
get_pmodule(void)
{
if (!pmodule) pmodule = g_strdup(get_pcomponent());
return pmodule;
}
void
push_component_module(
char *component,
char *module)
{
if (component_stack == NULL)
component_stack = g_queue_new();
g_queue_push_head(component_stack, pcomponent);
pcomponent = g_strdup(component);
if (module_stack == NULL)
module_stack = g_queue_new();
g_queue_push_head(module_stack, pmodule);
pmodule = g_strdup(module);
}
void
pop_component_module(void)
{
pcomponent = g_queue_pop_head(component_stack);
pmodule = g_queue_pop_head(module_stack);
}
#ifdef __OpenBSD__
void
openbsd_fd_inform(void)
{
int i;
for (i = DATA_FD_OFFSET; i < DATA_FD_OFFSET + DATA_FD_COUNT*2; i++) {
/* a simple fcntl() will cause the library to "look" at this file
* descriptor, which is good enough */
(void)fcntl(i, F_GETFL);
}
}
#endif
void
debug_executing(
GPtrArray *argv_ptr)
{
char *cmdline;
GString *strbuf;
gsize i, len = argv_ptr->len - 1;
/*
* This is ugly, but we have no choice: we cannot count on the caller to
* supply a NULL-terminated GPtrArray (he'll have other problems than this
* function, anyway), so we have to display all but the last argument - and
* therefore need to peek into the array directly to knows its length.
*
* To make things even funnier, we have to quote all arguments _except_ the
* command name (ie, the first one).
*/
strbuf = g_string_new((char *)g_ptr_array_index(argv_ptr, 0));
for (i = 1; i < len; i++) {
cmdline = g_shell_quote((char *)g_ptr_array_index(argv_ptr, i));
g_string_append_printf(strbuf, " %s", cmdline);
g_free(cmdline);
}
cmdline = g_string_free(strbuf, FALSE);
g_debug("Executing: %s", cmdline);
g_free(cmdline);
}
char *
get_first_line(
GPtrArray *argv_ptr)
{
char *output_string = NULL;
int inpipe[2], outpipe[2], errpipe[2];
int pid;
FILE *out, *err;
assert(argv_ptr != NULL);
assert(argv_ptr->pdata != NULL);
assert(argv_ptr->len >= 1);
if (pipe(inpipe) == -1) {
error(_("error [open pipe: %s]"), strerror(errno));
/*NOTREACHED*/
}
if (pipe(outpipe) == -1) {
error(_("error [open pipe: %s]"), strerror(errno));
/*NOTREACHED*/
}
if (pipe(errpipe) == -1) {
error(_("error [open pipe: %s]"), strerror(errno));
/*NOTREACHED*/
}
fflush(stdout);
switch(pid = fork()) {
case -1:
error(_("error [fork: %s]"), strerror(errno));
/*NOTREACHED*/
default: /* parent process */
aclose(inpipe[0]);
aclose(outpipe[1]);
aclose(errpipe[1]);
break;
case 0: /* child process */
aclose(inpipe[1]);
aclose(outpipe[0]);
aclose(errpipe[0]);
dup2(inpipe[0], 0);
dup2(outpipe[1], 1);
dup2(errpipe[1], 2);
debug_executing(argv_ptr);
g_fprintf(stdout, "unknown\n");
execv((char *)*argv_ptr->pdata, (char **)argv_ptr->pdata);
error(_("error [exec %s: %s]"), (char *)*argv_ptr->pdata, strerror(errno));
}
aclose(inpipe[1]);
out = fdopen(outpipe[0],"r");
err = fdopen(errpipe[0],"r");
if (out) {
output_string = pgets(out);
fclose(out);
}
if (err) {
if (!output_string)
output_string = pgets(err);
fclose(err);
}
waitpid(pid, NULL, 0);
return output_string;
}
gboolean
make_amanda_tmpdir(void)
{
struct stat stat_buf;
if (mkdir(AMANDA_TMPDIR, 0700) != 0) {
if (errno != EEXIST) {
g_debug("Error mkdir of AMANDA_TMPDIR (%s): %s", AMANDA_TMPDIR, strerror(errno));
return FALSE;
}
} else {
if (chown(AMANDA_TMPDIR, (int)get_client_uid(), (int)get_client_gid()) < 0) {
g_debug("Error chown of AMANDA_TMPDIR (%s): %s", AMANDA_TMPDIR, strerror(errno));
return FALSE;
}
}
if (stat(AMANDA_TMPDIR, &stat_buf) != 0) {
g_debug("Error doing a stat of AMANDA_TMPDIR (%s): %s", AMANDA_TMPDIR, strerror(errno));
return FALSE;
}
#ifdef CHECK_USERID
if (stat_buf.st_uid != get_client_uid()) {
g_debug("Error: Owner of AMANDA_TMPDIR (%s) is not %s\n", AMANDA_TMPDIR, CLIENT_LOGIN);
return FALSE;
}
#endif
if (stat_buf.st_mode & S_IWOTH) {
g_debug("Error: AMANDA_TMPDIR (%s) must not be writable by other\n", AMANDA_TMPDIR);
return FALSE;
}
return TRUE;
}
#define POLY 0x82F63B78
#if defined __x86_64__ || defined __i386__ || defined __i486__ || defined __i586__ || defined __i686__
static int get_sse42(void)
{
uint32_t op, eax, ebx, ecx, edx;
op = 1;
#ifdef __i386__
__asm__ volatile(
"pushl %%ebx;\n\t"
"cpuid;\n\t"
"movl %%ebx, %1;\n\t"
"popl %%ebx;\n\t"
: "=a" (eax), "=r" (ebx), "=c" (ecx), "=d" (edx)
: "a" (op)
: "cc"
);
#else
__asm__ volatile(
"cpuid;\n\t"
: "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx)
: "a" (op)
: "cc"
);
#endif
return (ecx >> 20) & 1;
}
#else
static int get_sse42(void)
{
return 0;
}
#endif
static uint32_t crc_table[16][256];
static gboolean crc_initialized = FALSE;
gboolean have_sse42 = FALSE;
void (* crc32_function)(uint8_t *buf, size_t len, crc_t *crc);
#include "amcrc32chw.h"
/* Run this function previously */
void
make_crc_table(void)
{
int i;
int j;
int slice;
if (!crc_initialized) {
if (compiled_with_sse4_2) {
have_sse42 = get_sse42();
}
if (have_sse42) {
crc32c_init_hw();
crc32_function = &crc32c_add_hw;
} else {
crc32_function = &crc32_add_16bytes;
}
for (i = 0; i < 256; i++) {
uint32_t c = i;
for (j = 0; j < 8; j++) {
c = (c & 1) ? (POLY ^ (c >> 1)) : (c >> 1);
}
crc_table[0][i] = c;
}
for (i = 0; i < 256; i++) {
for (slice = 1; slice < 16; slice++) {
crc_table[slice][i] = (crc_table[slice - 1][i] >> 8) ^ crc_table[0][crc_table[slice - 1][i] & 0xFF];
}
}
crc_initialized = TRUE;
}
}
void
crc32_init(
crc_t *crc)
{
make_crc_table();
crc->crc = 0xFFFFFFFF;
crc->size = 0;
}
#if defined __GNUC__ || defined __clang__
#define PREFETCH(location) __builtin_prefetch(location)
#else
#define PREFETCH(location)
#endif
void
crc32_add_1byte(
uint8_t *buf,
size_t len,
crc_t *crc)
{
crc->size += len;
while (len-- > 0) {
crc->crc = crc_table[0][(crc->crc ^ *buf++) & 0xFF] ^ (crc->crc >> 8);
}
}
#ifdef WORDS_BIGENDIAN
static inline uint32_t swap(uint32_t data);
static inline uint32_t
swap(
uint32_t data)
{
#if (defined(__GNUC__) && GCC_VERSION >= 40300) || defined __clang__
return __builtin_bswap32(data);
#else
return (data >> 24) |
((data >> 8) & 0x0000FF00) |
((data << 8) & 0x00FF0000) |
(data << 24);
#endif
}
#endif
void
crc32_add_16bytes(
uint8_t *buf,
size_t len,
crc_t *crc)
{
uint32_t *buf32 = (uint32_t *)buf;
size_t i;
crc->size += len;
while (len >= 256) {
PREFETCH(((const char*) buf) + 256);
for (i = 0; i < 4; i++) {
#ifdef WORDS_BIGENDIAN
uint32_t one = *buf32++ ^ swap(crc->crc);
uint32_t two = *buf32++;
uint32_t three = *buf32++;
uint32_t four = *buf32++;
crc->crc = crc_table[ 0][ four & 0xFF] ^
crc_table[ 1][(four >> 8) & 0xFF] ^
crc_table[ 2][(four >> 16) & 0xFF] ^
crc_table[ 3][(four >> 24) & 0xFF] ^
crc_table[ 4][ three & 0xFF] ^
crc_table[ 5][(three >> 8) & 0xFF] ^
crc_table[ 6][(three >> 16) & 0xFF] ^
crc_table[ 7][(three >> 24) & 0xFF] ^
crc_table[ 8][ two & 0xFF] ^
crc_table[ 9][(two >> 8) & 0xFF] ^
crc_table[10][(two >> 16) & 0xFF] ^
crc_table[11][(two >> 24) & 0xFF] ^
crc_table[12][ one & 0xFF] ^
crc_table[13][(one >> 8) & 0xFF] ^
crc_table[14][(one >> 16) & 0xFF] ^
crc_table[15][(one >> 24) & 0xFF];
#else
uint32_t one = *buf32++ ^ crc->crc;
uint32_t two = *buf32++;
uint32_t three = *buf32++;
uint32_t four = *buf32++;
crc->crc = crc_table[ 0][(four >> 24) & 0xFF] ^
crc_table[ 1][(four >> 16) & 0xFF] ^
crc_table[ 2][(four >> 8) & 0xFF] ^
crc_table[ 3][ four & 0xFF] ^
crc_table[ 4][(three >> 24) & 0xFF] ^
crc_table[ 5][(three >> 16) & 0xFF] ^
crc_table[ 6][(three >> 8) & 0xFF] ^
crc_table[ 7][ three & 0xFF] ^
crc_table[ 8][(two >> 24) & 0xFF] ^
crc_table[ 9][(two >> 16) & 0xFF] ^
crc_table[10][(two >> 8) & 0xFF] ^
crc_table[11][ two & 0xFF] ^
crc_table[12][(one >> 24) & 0xFF] ^
crc_table[13][(one >> 16) & 0xFF] ^
crc_table[14][(one >> 8) & 0xFF] ^
crc_table[15][ one & 0xFF];
#endif
}
len -= 4*16;
}
buf = (uint8_t *)buf32;
while (len-- > 0) {
crc->crc = crc_table[0][(crc->crc ^ *buf++) & 0xFF] ^ (crc->crc >> 8);
}
}
void
crc32_add(
uint8_t *buf,
size_t len,
crc_t *crc)
{
crc32_function(buf, len, crc);
return;
}
uint32_t
crc32_finish(
crc_t *crc)
{
return crc->crc ^ 0xFFFFFFFF;
}
void
parse_crc(
char *s,
crc_t *crc)
{
unsigned int c;
long long size;
if (sscanf(s, "%x:%lld", &c, &size) == 2) {
crc->crc = c;
crc->size = size;
} else {
crc->crc = 0;
crc->size = 0;
}
}
gint64
get_fsusage(
char *dir)
{
gint64 avail = 0;
struct fs_usage fsusage;
if (get_fs_usage(dir, NULL, &fsusage) == -1) {
return 0;
}
if (fsusage.fsu_bavail_top_bit_set)
avail = 0;
else {
avail = fsusage.fsu_bavail / 1024 * fsusage.fsu_blocksize;
}
return avail;
}
void
get_platform_and_distro(
char **r_platform,
char **r_distro)
{
char *uname = NULL;
char *distro = NULL;
char *platform = NULL;
char *productName = NULL;
char *productVersion = NULL;
char line[1025];
GPtrArray *argv_ptr;
FILE *release;
struct stat stat_buf;
if (!stat("/usr/bin/lsb_release", &stat_buf)) {
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, "/usr/bin/lsb_release");
g_ptr_array_add(argv_ptr, "--id");
g_ptr_array_add(argv_ptr, "-s");
g_ptr_array_add(argv_ptr, NULL);
distro = get_first_line(argv_ptr);
if (distro && distro[0] == '"') {
char *p= g_strdup(distro+1);
p[strlen(p)-1] = '\0';
g_free(distro);
distro = p;
}
g_ptr_array_free(argv_ptr, TRUE);
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, "/usr/bin/lsb_release");
g_ptr_array_add(argv_ptr, "--description");
g_ptr_array_add(argv_ptr, "-s");
g_ptr_array_add(argv_ptr, NULL);
platform = get_first_line(argv_ptr);
if (platform && platform[0] == '"') {
char *p= g_strdup(platform+1);
p[strlen(p)-1] = '\0';
g_free(platform);
platform = p;
}
g_ptr_array_free(argv_ptr, TRUE);
goto return_platorm;
}
release = fopen("/etc/redhat-release", "r");
if (release) {
char *result;
distro = g_strdup("RPM");
result = fgets(line, 1024, release);
if (result) {
platform = g_strdup(line);
}
fclose(release);
goto return_platorm;
}
release = fopen("/etc/lsb-release", "r");
if (release) {
distro = g_strdup("Ubuntu");
while (fgets(line, 1024, release)) {
if (strstr(line, "DISTRIB_ID")) {
char *p = strchr(line, '=');
if (p) {
g_free(distro);
distro = g_strdup(p+1);
}
}
if (strstr(line, "DESCRIPTION")) {
char *p = strchr(line, '=');
if (p) {
g_free(platform);
platform = g_strdup(p+1);
}
}
}
fclose(release);
goto return_platorm;
}
release = fopen("/etc/debian_version", "r");
if (release) {
char *result;
distro = g_strdup("Debian");
result = fgets(line, 1024, release);
if (result) {
platform = g_strdup(line);
}
fclose(release);
goto return_platorm;
}
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, UNAME_PATH);
g_ptr_array_add(argv_ptr, "-s");
g_ptr_array_add(argv_ptr, NULL);
uname = get_first_line(argv_ptr);
g_ptr_array_free(argv_ptr, TRUE);
if (uname) {
if (strncmp(uname, "SunOS", 5) == 0) {
FILE *release = fopen("/etc/release", "r");
distro = g_strdup("Solaris");
g_free(uname);
if (release) {
char *result;
result = fgets(line, 1024, release);
if (result) {
platform = g_strdup(line);
}
fclose(release);
goto return_platorm;
}
} else if (strlen(uname) >= 3 &&
g_strcasecmp(uname+strlen(uname)-3, "bsd") == 0) {
distro = uname;
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, UNAME_PATH);
g_ptr_array_add(argv_ptr, "-r");
g_ptr_array_add(argv_ptr, NULL);
platform = get_first_line(argv_ptr);
g_ptr_array_free(argv_ptr, TRUE);
} else {
g_free(uname);
}
}
if (!stat("/usr/bin/sw_vers", &stat_buf)) {
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, "/usr/bin/sw_vers");
g_ptr_array_add(argv_ptr, "-productName");
g_ptr_array_add(argv_ptr, NULL);
productName = get_first_line(argv_ptr);
g_ptr_array_free(argv_ptr, TRUE);
argv_ptr = g_ptr_array_new();
g_ptr_array_add(argv_ptr, "/usr/bin/sw_vers");
g_ptr_array_add(argv_ptr, "-productVersion");
g_ptr_array_add(argv_ptr, NULL);
productVersion = get_first_line(argv_ptr);
g_ptr_array_free(argv_ptr, TRUE);
if (productName && productVersion &&
!g_str_equal(productName, "unknown") &&
!g_str_equal( productVersion, "unknown")) {
distro = g_strdup("mac");
platform = g_strdup_printf("%s %s", productVersion, productVersion);
goto return_platorm;
}
}
return_platorm:
if (!distro) {
distro = g_strdup("Unknown");
}
if (!platform) {
platform = g_strdup("Unknown");
}
if (platform[strlen(platform) -1] == '\n') {
platform[strlen(platform) -1] = '\0';
}
if (r_platform) {
*r_platform = platform;
platform = NULL;
} else {
amfree(platform);
}
if (r_distro) {
*r_distro = distro;
distro = NULL;
} else {
amfree(distro);
}
amfree(productName);
amfree(productVersion);
}
char *
get_platform(void)
{
char *platform;
char *distro;
get_platform_and_distro(&platform, &distro);
amfree(distro);
return platform;
}
char *
get_distro(void)
{
char *platform;
char *distro;
get_platform_and_distro(&platform, &distro);
amfree(platform);
return distro;
}