/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * utils.c - generally useful, non-application specific functions for Gateway * */ #include "gw-config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gwlib.h" /* pid of child process when parachute is used */ static pid_t child_pid = -1; /* saved child signal handlers */ static struct sigaction child_actions[32]; /* just a flag that child signal handlers are stored */ static int child_actions_init = 0; /* our pid file name */ static char *pid_file = NULL; static volatile sig_atomic_t parachute_shutdown = 0; static void parachute_sig_handler(int signum) { info(0, "Signal %d received, forward to child pid (%ld)", signum, (long) child_pid); /* we do not handle any signal, just forward these to child process */ if (child_pid != -1 && getpid() != child_pid) kill(child_pid, signum); /* if signal received and no child there, terminating */ switch(signum) { case SIGTERM: case SIGINT: case SIGABRT: if (child_pid == -1) exit(0); else parachute_shutdown = 1; } } static void parachute_init_signals(int child) { struct sigaction sa; if (child_actions_init && child) { sigaction(SIGTERM, &child_actions[SIGTERM], NULL); sigaction(SIGQUIT, &child_actions[SIGQUIT], NULL); sigaction(SIGINT, &child_actions[SIGINT], NULL); sigaction(SIGABRT, &child_actions[SIGABRT], NULL); sigaction(SIGHUP, &child_actions[SIGHUP], NULL); sigaction(SIGALRM, &child_actions[SIGALRM], NULL); sigaction(SIGUSR1, &child_actions[SIGUSR1], NULL); sigaction(SIGUSR2, &child_actions[SIGUSR2], NULL); sigaction(SIGPIPE, &child_actions[SIGPIPE], NULL); } else if (!child && !child_actions_init) { sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = parachute_sig_handler; sigaction(SIGTERM, &sa, &child_actions[SIGTERM]); sigaction(SIGQUIT, &sa, &child_actions[SIGQUIT]); sigaction(SIGINT, &sa, &child_actions[SIGINT]); sigaction(SIGABRT, &sa, &child_actions[SIGABRT]); sigaction(SIGHUP, &sa, &child_actions[SIGHUP]); sigaction(SIGALRM, &sa, &child_actions[SIGALRM]); sigaction(SIGUSR1, &sa, &child_actions[SIGUSR1]); sigaction(SIGUSR2, &sa, &child_actions[SIGUSR2]); sa.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sa, &child_actions[SIGPIPE]); sigaction(SIGTTOU, &sa, NULL); sigaction(SIGTTIN, &sa, NULL); sigaction(SIGTSTP, &sa, NULL); child_actions_init = 1; } else panic(0, "Child process signal handlers not initialized before."); } static int is_executable(const char *filename) { struct stat buf; if (stat(filename, &buf)) { error(errno, "Error while stat of file `%s'", filename); return 0; } if (!S_ISREG(buf.st_mode) && !S_ISLNK(buf.st_mode)) { error(0, "File `%s' is not a regular file.", filename); return 0; } /* others has exec permission */ if (S_IXOTH & buf.st_mode) return 1; /* group has exec permission */ if ((S_IXGRP & buf.st_mode) && buf.st_gid == getgid()) return 1; /* owner has exec permission */ if ((S_IXUSR & buf.st_mode) && buf.st_uid == getuid()) return 1; return 0; } /* * become daemon. * returns 0 for father process; 1 for child process */ static int become_daemon(void) { int fd; if (getppid() != 1) { signal(SIGTTOU, SIG_IGN); signal(SIGTTIN, SIG_IGN); signal(SIGTSTP, SIG_IGN); if (fork()) return 0; setsid(); } close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); fd = open("/dev/null", O_RDWR); /* stdin */ if (fd == -1) panic(errno, "Could not open `/dev/null'"); dup(fd); /* stdout */ dup(fd); /* stderr */ chdir("/"); return 1; } #define PANIC_SCRIPT_MAX_LEN 4096 static PRINTFLIKE(2,3) void execute_panic_script(const char *panic_script, const char *format, ...) { char *args[3]; char buf[PANIC_SCRIPT_MAX_LEN + 1]; va_list ap; va_start(ap, format); vsnprintf(buf, PANIC_SCRIPT_MAX_LEN, format, ap); va_end(ap); if (fork()) return; close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); args[0] = (char*) panic_script; args[1] = buf; args[2] = NULL; execv(args[0], args); } static void parachute_start(const char *myname, const char *panic_script) { time_t last_start = 0, last_panic = 0; long respawn_count = 0; int status; if (panic_script && !is_executable(panic_script)) panic(0, "Panic script `%s' is not executable for us.", panic_script); /* setup sighandler */ parachute_init_signals(0); for (;;) { if (respawn_count > 0 && difftime(time(NULL), last_start) < 10) { error(0, "Child process died too fast, disabling for 30 sec."); gwthread_sleep(30.0); } if (!(child_pid = fork())) { /* child process */ parachute_init_signals(1); /* reset sighandlers */ return; } else if (child_pid < 0) { error(errno, "Could not start child process! Will retry in 5 sec."); gwthread_sleep(5.0); continue; } else { /* father process */ time(&last_start); info(0, "Child process with PID (%ld) started.", (long) child_pid); do { if (waitpid(child_pid, &status, 0) == child_pid) { /* check here why child terminated */ if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { info(0, "Child process exited gracefully, exit..."); gwlib_shutdown(); exit(0); } else if (WIFEXITED(status)) { error(0, "Caught child PID (%ld) which died with return code %d", (long) child_pid, WEXITSTATUS(status)); child_pid = -1; } else if (WIFSIGNALED(status)) { error(0, "Caught child PID (%ld) which died due to signal %d", (long) child_pid, WTERMSIG(status)); child_pid = -1; } } else if (errno != EINTR) { error(errno, "Error while waiting of child process."); } } while(child_pid > 0); if (parachute_shutdown) { /* may only happens if child process crashed while shutdown */ info(0, "Child process crashed while shutdown. Exiting due to signal..."); info(0, "Going into gwlib_shutdown..."); gwlib_shutdown(); info(0, "gwlib_shutdown done... Bye bye..."); exit(WIFEXITED(status) ? WEXITSTATUS(status) : 0); } /* check whether it's panic while start */ if (respawn_count == 0 && difftime(time(NULL), last_start) < 2) { info(0, "Child process crashed while starting. Exiting..."); info(0, "Going into gwlib_shutdown..."); gwlib_shutdown(); info(0, "gwlib_shutdown done... Bye bye..."); exit(WIFEXITED(status) ? WEXITSTATUS(status) : 1); } respawn_count++; if (panic_script && myname && difftime(time(NULL), last_panic) > 300) { time(&last_panic); debug("kannel", 0, "Executing panic script: %s %s %ld", panic_script, myname, respawn_count); execute_panic_script(panic_script, "%s %ld", myname, respawn_count); } /* sleep a while to get e.g. sockets released */ gwthread_sleep(5.0); } } } static void write_pid_file(void) { int fd; FILE *file; if (!pid_file) return; fd = open(pid_file, O_WRONLY|O_NOCTTY|O_TRUNC|O_CREAT|O_EXCL, 0644); if (fd == -1) panic(errno, "Could not open pid-file `%s'", pid_file); file = fdopen(fd, "w"); if (!file) panic(errno, "Could not open file-stream `%s'", pid_file); fprintf(file, "%ld\n", (long) getpid()); fclose(file); } static void remove_pid_file(void) { if (!pid_file) return; /* ensure we don't called from child process */ if (child_pid == 0) return; if (-1 == unlink(pid_file)) error(errno, "Could not unlink pid-file `%s'", pid_file); } static int change_user(const char *user) { struct passwd *pass; if (!user) return -1; pass = getpwnam(user); if (!pass) { error(0, "Could not find a user `%s' in system.", user); return -1; } if (-1 == setgid(pass->pw_gid)) { error(errno, "Could not change group id from %ld to %ld.", (long) getgid(), (long) pass->pw_gid); return -1; } if (initgroups(user, -1) == -1) { error(errno, "Could not set supplementary group ID's."); } if (-1 == setuid(pass->pw_uid)) { error(errno, "Could not change user id from %ld to %ld.", (long) getuid(), (long) pass->pw_uid); return -1; } return 0; } /* * new datatype functions */ MultibyteInt get_variable_value(Octet *source, int *len) { MultibyteInt retval = 0; for(*len=1;; (*len)++, source++) { retval = retval * 0x80 + (*source & 0x7F); if (*source < 0x80) /* if the continue-bit (high bit) is not set */ break; } return retval; } int write_variable_value(MultibyteInt value, Octet *dest) { int i, loc = 0; Octet revbuffer[20]; /* we write it backwards */ for (;;) { revbuffer[loc++] = (value & 0x7F) + 0x80; if (value >= 0x80) value = value >> 7; else break; } for(i=0; i < loc; i++) /* reverse the buffer */ dest[i] = revbuffer[loc-i-1]; dest[loc-1] &= 0x7F; /* remove trailer-bit from last */ return loc; } Octet reverse_octet(Octet source) { Octet dest; dest = (source & 1) <<7; dest += (source & 2) <<5; dest += (source & 4) <<3; dest += (source & 8) <<1; dest += (source & 16) >>1; dest += (source & 32) >>3; dest += (source & 64) >>5; dest += (source & 128) >>7; return dest; } int get_and_set_debugs(int argc, char **argv, int (*find_own) (int index, int argc, char **argv)) { int i, ret = -1; int debug_lvl = -1; int file_lvl = GW_DEBUG; char *log_file = NULL; char *debug_places = NULL; char *panic_script = NULL, *user = NULL; int parachute = 0, daemonize = 0; for (i=1; i < argc; i++) { if (strcmp(argv[i],"-v")==0 || strcmp(argv[i],"--verbosity")==0) { if (i+1 < argc) { debug_lvl = atoi(argv[i+1]); i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i],"-F")==0 || strcmp(argv[i],"--logfile")==0) { if (i+1 < argc && *(argv[i+1]) != '-') { log_file = argv[i+1]; i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i],"-V")==0 || strcmp(argv[i],"--fileverbosity")==0) { if (i+1 < argc) { file_lvl = atoi(argv[i+1]); i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i],"-D")==0 || strcmp(argv[i],"--debug")==0) { if (i+1 < argc) { debug_places = argv[i+1]; i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i], "-X")==0 || strcmp(argv[i], "--panic-script")==0) { if (i+1 < argc) { panic_script = argv[i+1]; i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i], "-P")==0 || strcmp(argv[i], "--parachute")==0) { parachute = 1; } else if (strcmp(argv[i], "-d")==0 || strcmp(argv[i], "--daemonize")==0) { daemonize = 1; } else if (strcmp(argv[i], "-p")==0 || strcmp(argv[i], "--pid-file")==0) { if (i+1 < argc) { pid_file = argv[i+1]; i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i], "-u")==0 || strcmp(argv[i], "--user")==0) { if (i+1 < argc) { user = argv[i+1]; i++; } else panic(0, "Missing argument for option %s\n", argv[i]); } else if (strcmp(argv[i], "-g")==0 || strcmp(argv[i], "--generate")==0) { cfg_dump_all(); exit(0); } else if (strcmp(argv[i],"--")==0) { i++; break; } else if (*argv[i] != '-') { break; } else { if (find_own != NULL) { ret = find_own(i, argc, argv); } if (ret < 0) { fprintf(stderr, "Unknown option %s, exiting.\n", argv[i]); panic(0, "Option parsing failed"); } else i += ret; /* advance additional args */ } } if (user && -1 == change_user(user)) panic(0, "Could not change to user `%s'.", user); /* deamonize */ if (daemonize && !become_daemon()) exit(0); if (pid_file) { write_pid_file(); atexit(remove_pid_file); } if (parachute) { /* * if we are running as daemon so open syslog * in order not to deal with i.e. log rotate. */ if (daemonize) { char *ident = strrchr(argv[0], '/'); if (!ident) ident = argv[0]; else ident++; log_set_syslog(ident, (debug_lvl > -1 ? debug_lvl : 0)); } parachute_start(argv[0], panic_script); /* now we are in child process so close syslog */ if (daemonize) log_close_all(); } if (debug_lvl > -1) log_set_output_level(debug_lvl); if (debug_places != NULL) log_set_debug_places(debug_places); if (log_file != NULL) log_open(log_file, file_lvl, GW_NON_EXCL); info(0, "Debug_lvl = %d, log_file = %s, log_lvl = %d", debug_lvl, log_file ? log_file : "", file_lvl); if (debug_places != NULL) info(0, "Debug places: `%s'", debug_places); return i; } static int pattern_matches_ip(Octstr *pattern, Octstr *ip) { long i, j; long pat_len, ip_len; int pat_c, ip_c; pat_len = octstr_len(pattern); ip_len = octstr_len(ip); i = 0; j = 0; while (i < pat_len && j < ip_len) { pat_c = octstr_get_char(pattern, i); ip_c = octstr_get_char(ip, j); if (pat_c == ip_c) { /* The characters match, go to the next ones. */ ++i; ++j; } else if (pat_c != '*') { /* They differ, and the pattern isn't a wildcard one. */ return 0; } else { /* We found a wildcard in the pattern. Skip in ip. */ ++i; while (j < ip_len && ip_c != '.') { ++j; ip_c = octstr_get_char(ip, j); } } } if (i >= pat_len && j >= ip_len) return 1; return 0; } static int pattern_list_matches_ip(Octstr *pattern_list, Octstr *ip) { List *patterns; Octstr *pattern; int matches; patterns = octstr_split(pattern_list, octstr_imm(";")); matches = 0; while (!matches && (pattern = gwlist_extract_first(patterns)) != NULL) { matches = pattern_matches_ip(pattern, ip); octstr_destroy(pattern); } gwlist_destroy(patterns, octstr_destroy_item); return matches; } int is_allowed_ip(Octstr *allow_ip, Octstr *deny_ip, Octstr *ip) { if (ip == NULL) return 0; if (octstr_len(deny_ip) == 0) return 1; if (allow_ip != NULL && pattern_list_matches_ip(allow_ip, ip)) return 1; if (pattern_list_matches_ip(deny_ip, ip)) return 0; return 1; } int connect_denied(Octstr *allow_ip, Octstr *ip) { if (ip == NULL) return 1; /* If IP not set, allow from Localhost */ if (allow_ip == NULL) { if (pattern_list_matches_ip(octstr_imm("127.0.0.1"), ip)) return 0; } else { if (pattern_list_matches_ip(allow_ip, ip)) return 0; } return 1; } int does_prefix_match(Octstr *prefix, Octstr *number) { /* XXX modify to use just octstr operations */ char *b, *p, *n; gw_assert(prefix != NULL); gw_assert(number != NULL); p = octstr_get_cstr(prefix); n = octstr_get_cstr(number); while (*p != '\0') { b = n; for (b = n; *b != '\0'; b++, p++) { if (*p == ';' || *p == '\0') { return 1; } if (*p != *b) break; } if (*p == ';' || *p == '\0') { return 1; } while (*p != '\0' && *p != ';') p++; while (*p == ';') p++; } return 0; } int normalize_number(char *dial_prefixes, Octstr **number) { char *t, *p, *official, *start; int len, official_len; if (dial_prefixes == NULL || dial_prefixes[0] == '\0') return 0; t = official = dial_prefixes; official_len = 0; gw_assert(number != NULL); while(1) { p = octstr_get_cstr(*number); for(start = t, len = 0; ; t++, p++, len++) { if (*t == ',' || *t == ';' || *t == '\0') { if (start != official) { Octstr *nstr; long n; if ( official[0] == '-' ) official_len=0; n = official_len; if (strlen(official) < (size_t) n) n = strlen(official); nstr = octstr_create_from_data(official, n); octstr_insert_data(nstr, official_len, octstr_get_cstr(*number) + len, octstr_len(*number) - len); octstr_destroy(*number); *number = nstr; } return 1; } if (*p == '\0' || *t != *p) break; /* not matching */ } for(; *t != ',' && *t != ';' && *t != '\0'; t++, len++) ; if (*t == '\0') break; if (start == official) official_len = len; if (*t == ';') official = t+1; t++; } return 0; } long decode_network_long(unsigned char *data) { return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; } void encode_network_long(unsigned char *data, unsigned long value) { data[0] = (value >> 24) & 0xff; data[1] = (value >> 16) & 0xff; data[2] = (value >> 8) & 0xff; data[3] = value & 0xff; } /* Something that does the same as GNU cfmakeraw. We don't use cfmakeraw so that we always know what it does, and also to reduce configure.in complexity. */ void kannel_cfmakeraw (struct termios *tio){ /* Block until a charactor is available, but it only needs to be one*/ tio->c_cc[VMIN] = 1; tio->c_cc[VTIME] = 0; /* GNU cfmakeraw sets these flags so we had better too...*/ /* Control modes */ /* Mask out character size (CSIZE), then set it to 8 bits (CS8). * Enable parity bit generation in both directions (PARENB). */ tio->c_cflag &= ~(CSIZE|PARENB); tio->c_cflag |= CS8; /* Input Flags,*/ /* Turn off all input flags that interfere with the byte stream: * BRKINT - generate SIGINT when receiving BREAK, ICRNL - translate * NL to CR, IGNCR - ignore CR, IGNBRK - ignore BREAK, * INLCR - translate NL to CR, IXON - use XON/XOFF flow control, * ISTRIP - strip off eighth bit. */ tio->c_iflag &= ~(BRKINT|ICRNL|IGNCR|IGNBRK|INLCR|IXON|ISTRIP); /* Other flags,*/ /* Turn off all local flags that interpret the byte stream: * ECHO - echo input chars, ECHONL - always echo NL even if ECHO is off, * ICANON - enable canonical mode (basically line-oriented mode), * IEXTEN - enable implementation-defined input processing, * ISIG - generate signals when certain characters are received. */ tio->c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN|ISIG); /* Output flags,*/ /* Disable implementation defined processing on the output stream*/ tio->c_oflag &= ~OPOST; } int gw_isdigit(int c) { return isdigit(c); } int gw_isxdigit(int c) { return isxdigit(c); } /* Rounds up the result of a division */ int roundup_div(int a, int b) { int t; t = a / b; if (t * b != a) t += 1; return t; } unsigned long long gw_generate_id(void) { /* create a 64 bit unique Id by putting a 32 bit epoch time value * and a 32 bit random value together */ unsigned long random, timer; random = gw_rand(); timer = (unsigned long)time(NULL); return ((unsigned long long)timer << 32) + random; }