spawn.c
| 1 |
/*
|
|---|---|
| 2 |
** Module : spawn.c
|
| 3 |
** Abstract : a simple tool able to spawn a process on an existing user account.
|
| 4 |
**
|
| 5 |
** Copyright (c) 2013-2025, Golden Code Development Corporation.
|
| 6 |
**
|
| 7 |
** -#- -I- --Date-- --------------------------------Description---------------------------------
|
| 8 |
** 001 MAG 20131127 First version.
|
| 9 |
** 002 MAG 20140205 Fix PTY (pseudo terminal) assignment bug.
|
| 10 |
** 003 CA 20140212 Added no password authentication: uses a secure P2J connection and temporary
|
| 11 |
** credentials to authenticate the request.
|
| 12 |
** 004 MAG 20140224 Redirect STDOUT to a file for batch clients.
|
| 13 |
** 005 MAG 20140310 A password argument has been added for agents. The password argument is
|
| 14 |
** required only on Windows OS. For Linux / Unix OS this argument is ignored.
|
| 15 |
** 006 ECF 20150907 Removed -XX:MaxPermSize=64m default option, which is no longer supported in
|
| 16 |
** Java 8.
|
| 17 |
** 007 EVL 20160217 Adding Solaris 10 u8 customization for ioctl.h to compile.
|
| 18 |
** 008 EVL 20160227 Adding missed getline() replacement.
|
| 19 |
** 009 EVL 20160323 Solaris version does not need the ioctl(fd, TIOCSCTTY,...) to be called.
|
| 20 |
** 010 GES 20160217 Clear the password buffer before freeing the pointer so that it doesn't stay
|
| 21 |
** in memory.
|
| 22 |
** 011 EVL 20200120 Added logic to handle valid passwordless user helping to avoid segmentation
|
| 23 |
** fault issue.
|
| 24 |
** 012 EVL 20200518 Fixed issue with handling extra debug option for application server spawning.
|
| 25 |
** 013 EVL 20210115 Adding more extra logging to FWD server.log for possible spawn failures.
|
| 26 |
** EVL 20210119 More debug info to output.
|
| 27 |
** IAS 20210325 Added addional parameters for command
|
| 28 |
** EVL 20210519 Fix for segmentation fault in strcmp() library call. Added new error codes to inform
|
| 29 |
** the caller about system errors.
|
| 30 |
** 014 GBB 20230131 outputToFile passed in as arg to create client process command.
|
| 31 |
** stdout redirect in Java.
|
| 32 |
** 015 GBB 20230512 The extra debug option removed. Logging levels attached to each message.
|
| 33 |
** 016 GBB 20230523 Cleanup of unused `j` var.
|
| 34 |
** 017 GBB 20230612 Logging the spawner chdir error.
|
| 35 |
** 018 GBB 20230623 Adding "started" print to stderr to notify the server logger to stop listening for logs.
|
| 36 |
** 019 RFB 20230418 Handle when a tilde is passed in as the working directory so it can be expanded
|
| 37 |
** appropriately. Ref. #4938.
|
| 38 |
** RFB 20230421 Added check for existence of the workdir to allow for separation of a missing
|
| 39 |
** directory from a permissions issue. Ref. #4938.
|
| 40 |
** RFB 20230425 Add some robustness to the string handling of added code per review.
|
| 41 |
** 020 GBB 20231213 Adding slf4j-impl.jar to JVM classpath.
|
| 42 |
** 021 EVL 20240117 Getting back the ability to redirect error stream to log inside native code.
|
| 43 |
** 022 CA 20240227 Added aspectjrt.jar to the spawner's classpath.
|
| 44 |
** 023 GBB 20240311 Started keyword removed. Spawner logger to stop listening based on spawn timeout.
|
| 45 |
** 024 GBB 20240729 Replace dependency slf4j-impl with slf4j-api in classpath.
|
| 46 |
** 025 CA 20250221 Added mode '2' to allow check of OS credentials (username/password).
|
| 47 |
** 026 EVL 20250625 Moved working with JVM to get spawn commands into childs process to completely release
|
| 48 |
** memory allocated with JVM start JNI command.
|
| 49 |
** EVL 20250714 Improved shared memory clean up/release after using completed.
|
| 50 |
*/
|
| 51 |
|
| 52 |
/*
|
| 53 |
** This program is free software: you can redistribute it and/or modify
|
| 54 |
** it under the terms of the GNU Affero General Public License as
|
| 55 |
** published by the Free Software Foundation, either version 3 of the
|
| 56 |
** License, or (at your option) any later version.
|
| 57 |
**
|
| 58 |
** This program is distributed in the hope that it will be useful,
|
| 59 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
| 60 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
| 61 |
** GNU Affero General Public License for more details.
|
| 62 |
**
|
| 63 |
** You may find a copy of the GNU Affero GPL version 3 at the following
|
| 64 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
|
| 65 |
**
|
| 66 |
** Additional terms under GNU Affero GPL version 3 section 7:
|
| 67 |
**
|
| 68 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional
|
| 69 |
** terms apply to the works covered under the License. These additional terms
|
| 70 |
** are non-permissive additional terms allowed under Section 7 of the GNU
|
| 71 |
** Affero GPL version 3 and may not be removed by you.
|
| 72 |
**
|
| 73 |
** 0. Attribution Requirement.
|
| 74 |
**
|
| 75 |
** You must preserve all legal notices or author attributions in the covered
|
| 76 |
** work or Appropriate Legal Notices displayed by works containing the covered
|
| 77 |
** work. You may not remove from the covered work any author or developer
|
| 78 |
** credit already included within the covered work.
|
| 79 |
**
|
| 80 |
** 1. No License To Use Trademarks.
|
| 81 |
**
|
| 82 |
** This license does not grant any license or rights to use the trademarks
|
| 83 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
|
| 84 |
** of Golden Code Development Corporation. You are not authorized to use the
|
| 85 |
** name Golden Code, FWD, or the names of any author or contributor, for
|
| 86 |
** publicity purposes without written authorization.
|
| 87 |
**
|
| 88 |
** 2. No Misrepresentation of Affiliation.
|
| 89 |
**
|
| 90 |
** You may not represent yourself as Golden Code Development Corporation or FWD.
|
| 91 |
**
|
| 92 |
** You may not represent yourself for publicity purposes as associated with
|
| 93 |
** Golden Code Development Corporation, FWD, or any author or contributor to
|
| 94 |
** the covered work, without written authorization.
|
| 95 |
**
|
| 96 |
** 3. No Misrepresentation of Source or Origin.
|
| 97 |
**
|
| 98 |
** You may not represent the covered work as solely your work. All modified
|
| 99 |
** versions of the covered work must be marked in a reasonable way to make it
|
| 100 |
** clear that the modified work is not originating from Golden Code Development
|
| 101 |
** Corporation or FWD. All modified versions must contain the notices of
|
| 102 |
** attribution required in this license.
|
| 103 |
*/
|
| 104 |
|
| 105 |
#define _GNU_SOURCE
|
| 106 |
#include <syslog.h> |
| 107 |
#include <sys/stat.h> |
| 108 |
#include <sys/types.h> |
| 109 |
#include <sys/shm.h> |
| 110 |
#include <sys/ioctl.h> |
| 111 |
#include <sys/wait.h> |
| 112 |
#include <unistd.h> |
| 113 |
#include <string.h> |
| 114 |
#include <pwd.h> |
| 115 |
#include <shadow.h> |
| 116 |
#include <signal.h> |
| 117 |
#include <grp.h> |
| 118 |
//#define _XOPEN_SOURCE 600
|
| 119 |
#include <stdio.h> |
| 120 |
#include <stdlib.h> |
| 121 |
#include <fcntl.h> |
| 122 |
#include <errno.h> |
| 123 |
#include <jni.h> |
| 124 |
|
| 125 |
#ifdef PAM
|
| 126 |
#include <security/pam_appl.h> |
| 127 |
#define PAM_SERVICE_NAME "common-auth" |
| 128 |
#else
|
| 129 |
#include <crypt.h> |
| 130 |
#endif
|
| 131 |
|
| 132 |
#define P2J_JAR "p2j.jar" |
| 133 |
#define KEY_STORE "srv-certs.store" |
| 134 |
|
| 135 |
#define MIN_ARGS_CREDS 3 |
| 136 |
#define MIN_ARGS_PASSWORD 4 |
| 137 |
#define MIN_ARGS_NO_PASSWORD 6 |
| 138 |
#define MIN_ARGS_COMMAND 4 |
| 139 |
#define EXIT_SUCCESS 0 |
| 140 |
|
| 141 |
#define ERR_SPAWN_NO_ROOT_PERMISSION -1 |
| 142 |
#define ERR_SPAWN_NO_ARGS -2 |
| 143 |
#define ERR_SPAWN_NO_PASSWORD -3 |
| 144 |
#define ERR_SPAWN_POSIX_OPENPT -4 |
| 145 |
#define ERR_SPAWN_GRANPT -5 |
| 146 |
#define ERR_SPAWN_UNLOCKPT -6 |
| 147 |
#define ERR_SPAWN_OPEN_SLAVE -7 |
| 148 |
#define ERR_SPAWN_IOCTL -8 |
| 149 |
#define ERR_SPAWN_SETGID -9 |
| 150 |
#define ERR_SPAWN_SETEGID -10 |
| 151 |
#define ERR_SPAWN_INITGROUPS -11 |
| 152 |
#define ERR_SPAWN_SETUID -12 |
| 153 |
#define ERR_SPAWN_SETEUID -13 |
| 154 |
#define ERR_SPAWN_SETSID -14 |
| 155 |
#define ERR_SPAWN_CHDIR -15 |
| 156 |
#define ERR_SPAWN_SETPGID -16 |
| 157 |
#define ERR_SPAWN_FORK_LEADER -17 |
| 158 |
#define ERR_SPAWN_FORK_COMMAND -18 |
| 159 |
#define ERR_SPAWN_CHDIR_WD -19 |
| 160 |
#define ERR_SPAWN_STAT_WD -20 |
| 161 |
#define ERR_SPAWN_SHMEM_GET -21 |
| 162 |
#define ERR_SPAWN_SHMEM_AT -22 |
| 163 |
#define ERR_SPAWN_SHMEM_DT -23 |
| 164 |
#define ERR_SPAWN_SHMEM_FREE -24 |
| 165 |
#define ERR_SPAWN_FORK_SHMEM -25 |
| 166 |
|
| 167 |
#define ERR_JNI_JVM_CREATE -100 |
| 168 |
#define ERR_JNI_P2J_ENTRY_POINT_CLASS -101 |
| 169 |
#define ERR_JNI_P2J_ENTRY_POINT_METHOD -102 |
| 170 |
#define ERR_JNI_REMOTE_SERVER_PORT -103 |
| 171 |
#define ERR_JNI_REMOTE_SERVER_HOST -104 |
| 172 |
#define ERR_JNI_REMOTE_SERVER_ALIAS -105 |
| 173 |
#define ERR_JNI_REMOTE_SERVER_UUID -106 |
| 174 |
#define ERR_JNI_REMOTE_COMMAND -107 |
| 175 |
#define ERR_JNI_JVM_DESTROY -108 |
| 176 |
#define ERR_JNI_REMOTE_SERVER_MISC -109 |
| 177 |
|
| 178 |
#define AUTH_OK 0 |
| 179 |
#define AUTH_ERR_USER 1 |
| 180 |
#define AUTH_ERR_PASSWORD 2 |
| 181 |
#define AUTH_ERR_ROOT 3 |
| 182 |
#define AUTH_ERR_PAM 4 |
| 183 |
#define AUTH_ERR_NULL_ACCOUNT_PASSWORD 5 |
| 184 |
#define AUTH_ERR_CRYPT 6 |
| 185 |
|
| 186 |
#define DEBUG_JVM 0 |
| 187 |
|
| 188 |
#define MAX_FILENAME 4096 |
| 189 |
/* Shared memory size, just two 4k pages to have enogh room to keep filename and commands */
|
| 190 |
#define SHM_SIZE 8192 |
| 191 |
#define SHM_PERM_RW_R_NO 0640 |
| 192 |
|
| 193 |
#ifdef SOLARIS
|
| 194 |
#define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */ |
| 195 |
#endif
|
| 196 |
|
| 197 |
/* prototypes */
|
| 198 |
void stdout_redirect();
|
| 199 |
|
| 200 |
/* Global data */
|
| 201 |
struct passwd *pw;
|
| 202 |
char message_buffer[MAX_FILENAME];
|
| 203 |
|
| 204 |
/* File descriptors */
|
| 205 |
int fdm, fds;
|
| 206 |
|
| 207 |
/* Output file name */
|
| 208 |
char output_file_name[MAX_FILENAME];
|
| 209 |
int output_to_file_opt_found = 0; |
| 210 |
/* Native log file name */
|
| 211 |
char log_file_name[MAX_FILENAME];
|
| 212 |
int native_log_found = 0; |
| 213 |
|
| 214 |
/**
|
| 215 |
* Use syslog to audit the execution of child processes.
|
| 216 |
*
|
| 217 |
* @param code
|
| 218 |
* Exit code.
|
| 219 |
* @param method
|
| 220 |
* Method name where error is throw.
|
| 221 |
*
|
| 222 |
* @return Exit code
|
| 223 |
*/
|
| 224 |
int audit(int code, const char *method) |
| 225 |
{
|
| 226 |
fprintf(stderr, "SEVERE: %d method:%s \n", code, method);
|
| 227 |
openlog("spawn", LOG_PID, LOG_USER);
|
| 228 |
syslog(LOG_ERR, "Error:%d method:%s (%m)\n", code, method);
|
| 229 |
closelog(); |
| 230 |
return code;
|
| 231 |
} |
| 232 |
|
| 233 |
/**
|
| 234 |
* Writes message to stderr if extra logging is required.
|
| 235 |
*
|
| 236 |
* @param msg
|
| 237 |
* The message to log.
|
| 238 |
*/
|
| 239 |
void logMessage(const char *msg) |
| 240 |
{
|
| 241 |
fprintf(stderr, "INFO: %s\n", msg);
|
| 242 |
} |
| 243 |
|
| 244 |
/**
|
| 245 |
* Assign a controlling terminal to a process.
|
| 246 |
*
|
| 247 |
* @return 0 - Success
|
| 248 |
* 1 - Error
|
| 249 |
*/
|
| 250 |
int assign_pty()
|
| 251 |
{
|
| 252 |
int result;
|
| 253 |
|
| 254 |
/* Open a pseudo-terminal device */
|
| 255 |
fdm = posix_openpt(O_RDWR); |
| 256 |
if (fdm < 0) |
| 257 |
{
|
| 258 |
return audit(ERR_SPAWN_POSIX_OPENPT, "posix_openpt"); |
| 259 |
} |
| 260 |
|
| 261 |
/* Grant access to the slave pseudo-terminal device */
|
| 262 |
result = grantpt(fdm); |
| 263 |
if (result != 0) |
| 264 |
{
|
| 265 |
return audit(ERR_SPAWN_GRANPT, "grantpt"); |
| 266 |
} |
| 267 |
|
| 268 |
/* Unlock a pseudo-terminal master/slave pair */
|
| 269 |
result = unlockpt(fdm); |
| 270 |
if (result != 0) |
| 271 |
{
|
| 272 |
return audit(ERR_SPAWN_UNLOCKPT, "unlockpt"); |
| 273 |
} |
| 274 |
|
| 275 |
/* open slave pseudo-terminal device */
|
| 276 |
fds = open(ptsname(fdm), O_RDWR); |
| 277 |
if (fds < 0) |
| 278 |
{
|
| 279 |
return audit(ERR_SPAWN_OPEN_SLAVE, "open(ptsname)"); |
| 280 |
} |
| 281 |
|
| 282 |
#ifndef SOLARIS
|
| 283 |
/* Use the slave as TTY */
|
| 284 |
if (ioctl(fds, TIOCSCTTY, (char *)1) == -1) |
| 285 |
{
|
| 286 |
return audit(ERR_SPAWN_IOCTL, "ioctl"); |
| 287 |
} |
| 288 |
#endif
|
| 289 |
|
| 290 |
return EXIT_SUCCESS;
|
| 291 |
} |
| 292 |
|
| 293 |
/**
|
| 294 |
* Expand the tilde in the given path.
|
| 295 |
* This should only be called if the first character of the given path
|
| 296 |
* is a tilde. It will expand that to the environment's HOME directory.
|
| 297 |
*
|
| 298 |
* @param path
|
| 299 |
* Path starting with tilde to expand.
|
| 300 |
* @param home
|
| 301 |
* The home directory.
|
| 302 |
*/
|
| 303 |
void expand_tilde(char *path, char *home) |
| 304 |
{
|
| 305 |
size_t homedir_len; |
| 306 |
char *homedir = (home != NULL) ? home : getenv("HOME"); |
| 307 |
homedir_len = strlen(homedir); |
| 308 |
|
| 309 |
/*
|
| 310 |
* Relocate the existing path information (starting with the character after the tilde) to the
|
| 311 |
* same buffer, but leave space for the homedir to be positioned with the memcpy().
|
| 312 |
* The existing null-terminator will also be relocated to the end of the path buffer.
|
| 313 |
*/
|
| 314 |
memmove(path + homedir_len, path + 1, strlen(path));
|
| 315 |
memcpy(path, homedir, homedir_len); |
| 316 |
|
| 317 |
return;
|
| 318 |
} |
| 319 |
|
| 320 |
/**
|
| 321 |
* Execute a command on a specified user account.
|
| 322 |
* A child process is created via fork().
|
| 323 |
* The child process uid and gid are set as the new user uid and gid.
|
| 324 |
* Create a new session for child. The child became a session leader.
|
| 325 |
* Assign a controlling terminal to session leader.
|
| 326 |
* The child process environment is set from the new user account.
|
| 327 |
* Create a child process to execute the command.
|
| 328 |
*
|
| 329 |
* @param command
|
| 330 |
* Command to execute
|
| 331 |
* @param argv
|
| 332 |
* Command arguments.
|
| 333 |
* @param workdir
|
| 334 |
* Process working directory.
|
| 335 |
*
|
| 336 |
* @return Process exit status.
|
| 337 |
*/
|
| 338 |
int spawn(const char* command, char* const argv[], const char* workdir) |
| 339 |
{
|
| 340 |
pid_t child_pid, proc_pid; |
| 341 |
int status, result;
|
| 342 |
|
| 343 |
/* Creates a child process */
|
| 344 |
child_pid = fork(); |
| 345 |
|
| 346 |
if (child_pid == 0) |
| 347 |
{
|
| 348 |
/* Set gid */
|
| 349 |
if (setgid(pw->pw_gid) < 0 || getgid() != pw->pw_gid) |
| 350 |
{
|
| 351 |
sprintf(message_buffer, "Unable to set GID for %d.", pw->pw_gid);
|
| 352 |
logMessage(message_buffer); |
| 353 |
exit(audit(ERR_SPAWN_SETGID, "setgid"));
|
| 354 |
} |
| 355 |
|
| 356 |
/* Set egit*/
|
| 357 |
if (setegid(pw->pw_gid) < 0 || getegid() != pw->pw_gid) |
| 358 |
{
|
| 359 |
sprintf(message_buffer, "Unable to set EGID for %d.", pw->pw_gid);
|
| 360 |
logMessage(message_buffer); |
| 361 |
exit(audit(ERR_SPAWN_SETEGID, "setegid"));
|
| 362 |
} |
| 363 |
|
| 364 |
/* Initialize the supplementary group access list */
|
| 365 |
if (initgroups(pw->pw_name, pw->pw_gid) < 0) |
| 366 |
{
|
| 367 |
sprintf(message_buffer, "Unable to init groups for PW_NAME %s and GID %d.", pw->pw_name, pw->pw_gid);
|
| 368 |
logMessage(message_buffer); |
| 369 |
exit(audit(ERR_SPAWN_INITGROUPS, "initgroups"));
|
| 370 |
} |
| 371 |
|
| 372 |
/* Set uid */
|
| 373 |
if (setuid(pw->pw_uid) < 0 || getuid() != pw->pw_uid) |
| 374 |
{
|
| 375 |
sprintf(message_buffer, "Unable to set UID for %d.", pw->pw_uid);
|
| 376 |
logMessage(message_buffer); |
| 377 |
exit(audit(ERR_SPAWN_SETUID, "setuid"));
|
| 378 |
} |
| 379 |
|
| 380 |
/* Set euid */
|
| 381 |
if (seteuid(pw->pw_uid) < 0 || geteuid() != pw->pw_uid) |
| 382 |
{
|
| 383 |
sprintf(message_buffer, "Unable to set EUID for %d.", pw->pw_uid);
|
| 384 |
logMessage(message_buffer); |
| 385 |
exit(audit(ERR_SPAWN_SETEUID, "seteuid"));
|
| 386 |
} |
| 387 |
|
| 388 |
/* Creates a session and sets the process group ID */
|
| 389 |
if(setsid() < 0) |
| 390 |
{
|
| 391 |
logMessage("Unable to set SID.");
|
| 392 |
exit(audit(ERR_SPAWN_SETSID, "setsid"));
|
| 393 |
} |
| 394 |
|
| 395 |
/* Assign controlling terminal */
|
| 396 |
result = assign_pty(); |
| 397 |
if (result != EXIT_SUCCESS)
|
| 398 |
{
|
| 399 |
logMessage("Unable to assign PTY.");
|
| 400 |
exit(audit(result, "assign_pty"));
|
| 401 |
} |
| 402 |
|
| 403 |
/* Set user environment */
|
| 404 |
char* path = strdup(getenv("PATH")); |
| 405 |
setenv("HOME",pw->pw_dir,1); |
| 406 |
setenv("USER",pw->pw_name,1); |
| 407 |
setenv("LOGNAME",pw->pw_name,1); |
| 408 |
setenv("LOGIN",pw->pw_name,1); /*< Historical; only strictly needed on AIX */ |
| 409 |
setenv("SHELL",pw->pw_shell[0] ? pw->pw_shell : "/bin/sh", 1); |
| 410 |
setenv("PATH",path,1); |
| 411 |
free(path); |
| 412 |
|
| 413 |
/* Change the current working directory */
|
| 414 |
char* chHomeDir = getenv("HOME"); |
| 415 |
if (chdir(chHomeDir) < 0) |
| 416 |
{
|
| 417 |
if (chdir("/") < 0) |
| 418 |
{
|
| 419 |
sprintf(message_buffer, "Unable to enter the home directory - %s.", chHomeDir);
|
| 420 |
logMessage(message_buffer); |
| 421 |
exit(audit(ERR_SPAWN_CHDIR, "chdir($HOME)"));
|
| 422 |
} |
| 423 |
} |
| 424 |
|
| 425 |
/* Spawn a child process */
|
| 426 |
proc_pid = fork(); |
| 427 |
|
| 428 |
if (proc_pid == 0) |
| 429 |
{
|
| 430 |
/* Close master pty */
|
| 431 |
close(fdm); |
| 432 |
|
| 433 |
/*
|
| 434 |
* Redirect STDIN and STDOUT to PTY.
|
| 435 |
* STDERR is always inherited.
|
| 436 |
*/
|
| 437 |
dup2(fds, STDIN_FILENO); // PTY becomes standard input (0)
|
| 438 |
dup2(fds, STDOUT_FILENO); // PTY becomes standard output (1)
|
| 439 |
|
| 440 |
/* Cancel certain signals */
|
| 441 |
signal(SIGCHLD,SIG_DFL); /* A child process dies */
|
| 442 |
signal(SIGTSTP,SIG_IGN); /* Various TTY signals */
|
| 443 |
signal(SIGTTOU,SIG_IGN); |
| 444 |
signal(SIGTTIN,SIG_IGN); |
| 445 |
signal(SIGHUP, SIG_IGN); /* Ignore hangup signal */
|
| 446 |
signal(SIGTERM,SIG_DFL); /* Die on SIGTERM */
|
| 447 |
|
| 448 |
/* Set process group ID */
|
| 449 |
if (setpgid(0, child_pid) < 0) |
| 450 |
{
|
| 451 |
sprintf(message_buffer, "Forked. Unable to set PGID for %d.", child_pid);
|
| 452 |
logMessage(message_buffer); |
| 453 |
exit(audit(ERR_SPAWN_SETPGID, "setpgid"));
|
| 454 |
} |
| 455 |
|
| 456 |
/* Set process working directory */
|
| 457 |
char *expanded_workdir;
|
| 458 |
size_t expanded_workdir_len = strlen(workdir) + strlen(pw->pw_dir) + 1;
|
| 459 |
expanded_workdir = malloc(sizeof(char) * expanded_workdir_len); |
| 460 |
strcpy(expanded_workdir, workdir); |
| 461 |
|
| 462 |
if (expanded_workdir[0] == '~' && expanded_workdir[1] == '/') |
| 463 |
{
|
| 464 |
expand_tilde(expanded_workdir, pw->pw_dir); |
| 465 |
} |
| 466 |
|
| 467 |
struct stat sb;
|
| 468 |
if (stat(expanded_workdir, &sb) == 0 && S_ISDIR(sb.st_mode)) |
| 469 |
{
|
| 470 |
snprintf(message_buffer, sizeof(message_buffer), "Forked. Setting working directory to %s.", |
| 471 |
expanded_workdir); |
| 472 |
logMessage(message_buffer); |
| 473 |
if (chdir(expanded_workdir))
|
| 474 |
{
|
| 475 |
snprintf(message_buffer, sizeof(message_buffer),
|
| 476 |
"Forked. Unable to set working directory to %s. Error: %s, errno is %d",
|
| 477 |
expanded_workdir, |
| 478 |
strerror(errno), |
| 479 |
errno); |
| 480 |
logMessage(message_buffer); |
| 481 |
free(expanded_workdir); |
| 482 |
exit(audit(ERR_SPAWN_CHDIR_WD, "chdir(expanded_workdir)"));
|
| 483 |
} |
| 484 |
} |
| 485 |
else
|
| 486 |
{
|
| 487 |
snprintf(message_buffer, sizeof(message_buffer), "Forked. Working directory (%s) does not exist.", |
| 488 |
expanded_workdir); |
| 489 |
logMessage(message_buffer); |
| 490 |
free(expanded_workdir); |
| 491 |
exit(audit(ERR_SPAWN_STAT_WD, "stat(expanded_workdir)"));
|
| 492 |
} |
| 493 |
free(expanded_workdir); |
| 494 |
|
| 495 |
/* Redirect STDOUT if required */
|
| 496 |
if (native_log_found == 1) |
| 497 |
{
|
| 498 |
stdout_redirect(); |
| 499 |
} |
| 500 |
|
| 501 |
/* Execute command */
|
| 502 |
exit(execvp(command, argv)); |
| 503 |
} |
| 504 |
else if (proc_pid < 0) |
| 505 |
{
|
| 506 |
/* On fork() fails */
|
| 507 |
logMessage("Failure in fork() call.");
|
| 508 |
exit(audit(ERR_SPAWN_FORK_COMMAND, "fork"));
|
| 509 |
} |
| 510 |
|
| 511 |
/* Close slave pty */
|
| 512 |
close(fds); |
| 513 |
|
| 514 |
/* Wait for child to exit*/
|
| 515 |
wait(&status); |
| 516 |
|
| 517 |
/* Exit */
|
| 518 |
exit(status); |
| 519 |
} |
| 520 |
else if (child_pid < 0) |
| 521 |
{
|
| 522 |
/* On fork() fails */
|
| 523 |
logMessage("Failure in child process creation.");
|
| 524 |
exit(audit(ERR_SPAWN_FORK_LEADER, "fork"));
|
| 525 |
} |
| 526 |
|
| 527 |
return EXIT_SUCCESS;
|
| 528 |
} |
| 529 |
|
| 530 |
#ifdef PAM
|
| 531 |
struct pam_response *reply;
|
| 532 |
|
| 533 |
/**
|
| 534 |
* PAM conversation function.
|
| 535 |
* This is a minimal implementation.
|
| 536 |
*
|
| 537 |
* @param num_msg
|
| 538 |
* The number of messages that are being passed to the function.
|
| 539 |
* @param msg
|
| 540 |
* A pointer to the buffer that holds the messages from the user.
|
| 541 |
* @param resp
|
| 542 |
* A pointer to the buffer that holds the responses to the user.
|
| 543 |
* @param appdata_ptr
|
| 544 |
* Pointer to the application data.
|
| 545 |
*
|
| 546 |
* @return PAM_SUCCESS - success.
|
| 547 |
*/
|
| 548 |
int function_conversation(int num_msg, |
| 549 |
const struct pam_message **msg, |
| 550 |
struct pam_response **resp,
|
| 551 |
void *appdata_ptr)
|
| 552 |
{
|
| 553 |
*resp = reply; |
| 554 |
return PAM_SUCCESS;
|
| 555 |
} |
| 556 |
|
| 557 |
/**
|
| 558 |
* PAM authentication.
|
| 559 |
*
|
| 560 |
* @param username
|
| 561 |
* User name.
|
| 562 |
* @param password
|
| 563 |
* Use password.
|
| 564 |
*
|
| 565 |
* @return AUTH_OK - success.
|
| 566 |
* AUTH_ERR_USER - user does not exists.
|
| 567 |
* AUTH_ERR_PAM - on other errors.
|
| 568 |
*/
|
| 569 |
int check_user(const char *username, char *password) |
| 570 |
{
|
| 571 |
int retval;
|
| 572 |
const char *user; |
| 573 |
pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start |
| 574 |
const struct pam_conv local_conversation = { function_conversation, NULL }; |
| 575 |
|
| 576 |
/* The pam_start function creates the PAM context and initiates the PAM transaction. */
|
| 577 |
retval = pam_start(PAM_SERVICE_NAME, username, &local_conversation, &local_auth_handle); |
| 578 |
|
| 579 |
if (retval != PAM_SUCCESS)
|
| 580 |
{
|
| 581 |
return audit(AUTH_ERR_PAM, "pam_start"); |
| 582 |
} |
| 583 |
|
| 584 |
if (password != NULL) |
| 585 |
{
|
| 586 |
reply = (struct pam_response *)malloc(sizeof(struct pam_response)); |
| 587 |
|
| 588 |
reply->resp = password; |
| 589 |
reply->resp_retcode = 0;
|
| 590 |
|
| 591 |
/* The pam_authenticate function is used to authenticate the user. */
|
| 592 |
retval = pam_authenticate(local_auth_handle, 0);
|
| 593 |
|
| 594 |
if (retval != PAM_SUCCESS)
|
| 595 |
{
|
| 596 |
switch (retval)
|
| 597 |
{
|
| 598 |
case PAM_USER_UNKNOWN:
|
| 599 |
return audit(AUTH_ERR_USER, "pam_authenticate"); |
| 600 |
default:
|
| 601 |
return audit(AUTH_ERR_PAM, "pam_authenticate"); |
| 602 |
} |
| 603 |
} |
| 604 |
} |
| 605 |
|
| 606 |
/* Get mapped user name; PAM may have changed it */
|
| 607 |
retval = pam_get_item(local_auth_handle, PAM_USER, (const void **)&user); |
| 608 |
if (retval != PAM_SUCCESS)
|
| 609 |
{
|
| 610 |
return audit(AUTH_ERR_USER, "pam_get_item"); |
| 611 |
} |
| 612 |
|
| 613 |
/* Get a pointer to a structure containing the broken-out fields
|
| 614 |
of the record in the password database (e.g., the local password
|
| 615 |
file /etc/passwd, NIS, and LDAP) that matches the username name. */
|
| 616 |
pw = getpwnam(user); |
| 617 |
endpwent(); |
| 618 |
|
| 619 |
if (!pw) return audit(AUTH_ERR_USER, "getpwnam"); //user doesn't really exist |
| 620 |
|
| 621 |
if (pw->pw_uid == 0) |
| 622 |
{
|
| 623 |
return AUTH_ERR_ROOT; // user is root |
| 624 |
} |
| 625 |
|
| 626 |
/* The pam_end function terminates the PAM transaction and is the
|
| 627 |
last function an application should call in the PAM context. */
|
| 628 |
retval = pam_end(local_auth_handle, retval); |
| 629 |
|
| 630 |
if (retval != PAM_SUCCESS)
|
| 631 |
{
|
| 632 |
return audit(AUTH_ERR_PAM, "pam_end"); |
| 633 |
} |
| 634 |
|
| 635 |
return AUTH_OK;
|
| 636 |
} |
| 637 |
|
| 638 |
#else
|
| 639 |
|
| 640 |
/**
|
| 641 |
* Get user account informations including uid, gid and password.
|
| 642 |
* If the user exists check for password match.
|
| 643 |
* This is basically an authentication action.
|
| 644 |
* The user root is not allowed.
|
| 645 |
*
|
| 646 |
* @param username
|
| 647 |
* User name.
|
| 648 |
* @param password
|
| 649 |
* Use password.
|
| 650 |
*
|
| 651 |
* @return AUTH_OK - success.
|
| 652 |
* AUTH_ERR_USER - user does not exists.
|
| 653 |
* AUTH_ERR_PASSWORD - password does not match.
|
| 654 |
* AUTH_ERR_ROOT - user is root.
|
| 655 |
*/
|
| 656 |
int check_user(const char* username, const char* password) |
| 657 |
{
|
| 658 |
struct spwd *sp;
|
| 659 |
char *encrypted, *correct;
|
| 660 |
int rc;
|
| 661 |
|
| 662 |
sprintf(message_buffer, "Check user account for: %s.", username);
|
| 663 |
logMessage(message_buffer); |
| 664 |
|
| 665 |
/* Get a pointer to a structure containing the broken-out fields
|
| 666 |
of the record in the password database (e.g., the local password
|
| 667 |
file /etc/passwd, NIS, and LDAP) that matches the username name. */
|
| 668 |
pw = getpwnam(username); |
| 669 |
endpwent(); |
| 670 |
|
| 671 |
if (!pw) return audit(AUTH_ERR_USER, "getpwnam"); //user doesn't really exist |
| 672 |
|
| 673 |
if (pw->pw_uid == 0) |
| 674 |
{
|
| 675 |
return AUTH_ERR_ROOT; // user is root |
| 676 |
} |
| 677 |
|
| 678 |
if (password != NULL) |
| 679 |
{
|
| 680 |
logMessage("The password is not null.");
|
| 681 |
/* Get a pointer to a structure containing the broken-out fields
|
| 682 |
of the record in the shadow password database that matches the username name. */
|
| 683 |
sp = getspnam(pw->pw_name); |
| 684 |
endspent(); |
| 685 |
|
| 686 |
if (sp)
|
| 687 |
{
|
| 688 |
logMessage("GetSPNam not null, used password from structure.");
|
| 689 |
correct = sp->sp_pwdp; |
| 690 |
} |
| 691 |
else
|
| 692 |
{
|
| 693 |
logMessage("GetSPNam is null, used password from passwd instead.");
|
| 694 |
correct = pw->pw_passwd; |
| 695 |
} |
| 696 |
|
| 697 |
// here we separate passwordless user from missing password for regular user
|
| 698 |
if (sp && strlen(password) == 0 && strlen(correct) == 1 && correct[0] == '!') |
| 699 |
{
|
| 700 |
logMessage("Empty password login.");
|
| 701 |
return AUTH_OK;
|
| 702 |
} |
| 703 |
|
| 704 |
// internal error case
|
| 705 |
if (correct == NULL) |
| 706 |
{
|
| 707 |
sprintf(message_buffer, |
| 708 |
"No password set for user account '%s', cannot compare against non-null input password.",
|
| 709 |
username); |
| 710 |
logMessage(message_buffer); |
| 711 |
return AUTH_ERR_NULL_ACCOUNT_PASSWORD;
|
| 712 |
} |
| 713 |
|
| 714 |
encrypted = crypt(password, correct); |
| 715 |
|
| 716 |
// need to check to avoid NULL pointer in srtcmp()
|
| 717 |
if (encrypted == NULL) |
| 718 |
{
|
| 719 |
sprintf(message_buffer, "Password encryption error: %s, errno is %d.", strerror(errno), errno);
|
| 720 |
logMessage(message_buffer); |
| 721 |
// code 22 - invalid argument
|
| 722 |
if (errno == EINVAL)
|
| 723 |
{
|
| 724 |
sprintf(message_buffer, "Check the password setup for user '%s'.", username);
|
| 725 |
logMessage(message_buffer); |
| 726 |
} |
| 727 |
return AUTH_ERR_CRYPT;
|
| 728 |
} |
| 729 |
|
| 730 |
logMessage("Attempt to use login with password.");
|
| 731 |
// bad pw=2, success=0
|
| 732 |
rc = strcmp(encrypted, correct); |
| 733 |
|
| 734 |
sprintf(message_buffer, "Passwords comparison result: %d.", rc);
|
| 735 |
logMessage(message_buffer); |
| 736 |
|
| 737 |
return rc ? AUTH_ERR_PASSWORD : AUTH_OK;
|
| 738 |
} |
| 739 |
else
|
| 740 |
{
|
| 741 |
logMessage("The password is null.");
|
| 742 |
return AUTH_OK;
|
| 743 |
} |
| 744 |
} |
| 745 |
|
| 746 |
#endif
|
| 747 |
|
| 748 |
/**
|
| 749 |
* Check executable file attributes.
|
| 750 |
*
|
| 751 |
* @param path
|
| 752 |
* path to executable file.
|
| 753 |
* @param permissions
|
| 754 |
* The file's permissions
|
| 755 |
*
|
| 756 |
* @return 0 - OK.
|
| 757 |
* 1 - No root permissions.
|
| 758 |
*/
|
| 759 |
int check_root(const char *path, int permissions) |
| 760 |
{
|
| 761 |
struct stat pstat;
|
| 762 |
int result;
|
| 763 |
|
| 764 |
result = stat(path, &pstat); |
| 765 |
|
| 766 |
if (!result)
|
| 767 |
{
|
| 768 |
result = (pstat.st_uid == 0) &&
|
| 769 |
(pstat.st_gid == 0) &&
|
| 770 |
(pstat.st_mode & ALLPERMS) == permissions ? 0 : 1; |
| 771 |
} |
| 772 |
else
|
| 773 |
{
|
| 774 |
audit(result, "stat");
|
| 775 |
} |
| 776 |
|
| 777 |
return result;
|
| 778 |
} |
| 779 |
|
| 780 |
#ifdef SOLARIS
|
| 781 |
#define _BUFSIZE_GET_LINE 1024 |
| 782 |
#define getline get_line
|
| 783 |
|
| 784 |
/**
|
| 785 |
* Get line of characters from the stream.
|
| 786 |
*
|
| 787 |
* @param lineptr
|
| 788 |
* pointer to the buffer to store the line.
|
| 789 |
* @param line_size
|
| 790 |
* the size of the line buffer.
|
| 791 |
* @param stream
|
| 792 |
* the stream to read the data from.
|
| 793 |
*
|
| 794 |
* @return The number of chars read from the stream or -1 if EOF encountered before any chars.
|
| 795 |
*/
|
| 796 |
ssize_t get_line(char **line_ptr, size_t *line_size, FILE *stream)
|
| 797 |
{
|
| 798 |
// the internal line buffer
|
| 799 |
char * buff;
|
| 800 |
// bytes in currently allocated buffer
|
| 801 |
size_t bytes_allocated = 0;
|
| 802 |
// current character
|
| 803 |
int curr_char;
|
| 804 |
// the length of the chars already being read
|
| 805 |
size_t llength = 0;
|
| 806 |
|
| 807 |
// check if we need to allocate memory for line buffer
|
| 808 |
if (*line_ptr == NULL) |
| 809 |
{
|
| 810 |
// need to allocate new buffer
|
| 811 |
bytes_allocated = _BUFSIZE_GET_LINE; |
| 812 |
buff = malloc(sizeof(char) * bytes_allocated); |
| 813 |
} |
| 814 |
else
|
| 815 |
{
|
| 816 |
// buffer already exists
|
| 817 |
buff = *line_ptr; |
| 818 |
bytes_allocated = *line_size; |
| 819 |
} |
| 820 |
|
| 821 |
// read buffer until new line char or EOF
|
| 822 |
do
|
| 823 |
{
|
| 824 |
// get the next character
|
| 825 |
curr_char = fgetc(stream); |
| 826 |
|
| 827 |
// end of file - do nothing for char storing
|
| 828 |
if (curr_char != EOF) |
| 829 |
{
|
| 830 |
// no moe space in a buffer - need to reallocate
|
| 831 |
if (llength >= bytes_allocated)
|
| 832 |
{
|
| 833 |
// new size is old plus buffer size
|
| 834 |
bytes_allocated += _BUFSIZE_GET_LINE; |
| 835 |
buff = realloc(buff, sizeof(char) * bytes_allocated); |
| 836 |
} |
| 837 |
|
| 838 |
// store the next char
|
| 839 |
*(buff + llength) = (unsigned char)curr_char; |
| 840 |
llength++; |
| 841 |
} |
| 842 |
|
| 843 |
} while (curr_char != '\n' && curr_char != EOF); |
| 844 |
|
| 845 |
// check the results
|
| 846 |
if (llength == 0) |
| 847 |
{
|
| 848 |
// buffer was allocated here, need to free memory
|
| 849 |
if (buff != NULL && *line_ptr == NULL) |
| 850 |
{
|
| 851 |
free(buff); |
| 852 |
buff = NULL;
|
| 853 |
} |
| 854 |
// result must be -1 meaning error
|
| 855 |
llength = -1;
|
| 856 |
// setting the error value to I/O error
|
| 857 |
errno = EIO; |
| 858 |
} |
| 859 |
else
|
| 860 |
{
|
| 861 |
// buffer is not empty, set up the NULL terminated string
|
| 862 |
if (buff != NULL) |
| 863 |
{
|
| 864 |
buff[llength] = '\0';
|
| 865 |
// return the buffer too including possible new line character
|
| 866 |
*line_ptr = buff; |
| 867 |
} |
| 868 |
else
|
| 869 |
{
|
| 870 |
// this is pretty impossible case, meeans something is wrong, return -1 meaning error
|
| 871 |
llength = -1;
|
| 872 |
// let's consider we have no memory to allocate
|
| 873 |
errno = ENOMEM; |
| 874 |
} |
| 875 |
} |
| 876 |
// store the bytes required to allocate
|
| 877 |
*line_size = bytes_allocated; |
| 878 |
|
| 879 |
return llength;
|
| 880 |
} |
| 881 |
#endif
|
| 882 |
|
| 883 |
/**
|
| 884 |
* Overwrite the given buffer to erase the contained data and free the pointer.
|
| 885 |
*
|
| 886 |
* @param buf
|
| 887 |
* The buffer to zap.
|
| 888 |
* @param len
|
| 889 |
* The length of the buffer.
|
| 890 |
*/
|
| 891 |
void zap(char** buf, size_t* len) |
| 892 |
{
|
| 893 |
if (*buf != NULL) |
| 894 |
{
|
| 895 |
int i;
|
| 896 |
|
| 897 |
int z = *len;
|
| 898 |
char* ptr = *buf;
|
| 899 |
|
| 900 |
for (i = 0; i < z; i++) |
| 901 |
{
|
| 902 |
*(ptr + i) = 0;
|
| 903 |
} |
| 904 |
|
| 905 |
free(ptr); |
| 906 |
*buf = NULL;
|
| 907 |
} |
| 908 |
|
| 909 |
/* make sure our caller doesn't get confused */
|
| 910 |
*len = 0;
|
| 911 |
} |
| 912 |
|
| 913 |
/**
|
| 914 |
* Get the user password from stdin.
|
| 915 |
*
|
| 916 |
* @param len
|
| 917 |
* On output, this will be set to the length of the returned buffer.
|
| 918 |
*
|
| 919 |
* @return The read password or NULL on error.
|
| 920 |
*/
|
| 921 |
char* password(size_t* len)
|
| 922 |
{
|
| 923 |
/* by setting these to NULL and 0, getline() will malloc() a buffer */
|
| 924 |
char* pswd = NULL; |
| 925 |
*len = 0;
|
| 926 |
|
| 927 |
/* read a line from the console */
|
| 928 |
int read = getline(&pswd, len, stdin);
|
| 929 |
|
| 930 |
sprintf(message_buffer, "The password length(data read): %lu(%d).", *len, read);
|
| 931 |
logMessage(message_buffer); |
| 932 |
|
| 933 |
if (read == -1) |
| 934 |
{
|
| 935 |
sprintf(message_buffer, "The password read error: %d.", errno);
|
| 936 |
logMessage(message_buffer); |
| 937 |
if (errno == 0) |
| 938 |
{
|
| 939 |
// considering EOF encountered
|
| 940 |
if (pswd == NULL) |
| 941 |
{
|
| 942 |
pswd = malloc(1);
|
| 943 |
} |
| 944 |
else
|
| 945 |
{
|
| 946 |
pswd = realloc(pswd, 1);
|
| 947 |
} |
| 948 |
pswd[0] = '\0'; |
| 949 |
} |
| 950 |
/* error has occurred, not sure if this can happen but we are being safe */
|
| 951 |
else if (pswd != NULL) |
| 952 |
{
|
| 953 |
zap(&pswd, len); |
| 954 |
} |
| 955 |
} |
| 956 |
else if (read == 0) |
| 957 |
{
|
| 958 |
if (pswd == NULL) |
| 959 |
{
|
| 960 |
pswd = malloc(1);
|
| 961 |
} |
| 962 |
else
|
| 963 |
{
|
| 964 |
pswd = realloc(pswd, 1);
|
| 965 |
} |
| 966 |
pswd[0] = '\0'; |
| 967 |
} |
| 968 |
else if (pswd[read - 1] == '\n') |
| 969 |
{
|
| 970 |
/* remove the newline if it is there */
|
| 971 |
pswd[read - 1] = 0; |
| 972 |
} |
| 973 |
|
| 974 |
return pswd;
|
| 975 |
} |
| 976 |
|
| 977 |
/**
|
| 978 |
* Print help.
|
| 979 |
*
|
| 980 |
* @param pname
|
| 981 |
* The program's name.
|
| 982 |
*/
|
| 983 |
int help(char *pname) |
| 984 |
{
|
| 985 |
printf("Usage:\n");
|
| 986 |
printf("- for password authentication: %s 1 <user> <workdir> <command> [args]\n", pname);
|
| 987 |
printf("- for server authentication: %s 0 <secure-port> <host> <server-alias> <uuid>\n", pname);
|
| 988 |
printf("- for user credentials check: %s 2 <user>\n", pname);
|
| 989 |
|
| 990 |
return EXIT_SUCCESS;
|
| 991 |
} |
| 992 |
|
| 993 |
/**
|
| 994 |
* Launch an in-process JVM and connects to the specified server. Authentication is assumed passed
|
| 995 |
* only if a secure connection can be establiashed with the server and the server's certificate
|
| 996 |
* matches one from the store.
|
| 997 |
* <p>
|
| 998 |
* If authentication passes, starts a new process with the real command.
|
| 999 |
*
|
| 1000 |
* @param cport
|
| 1001 |
* The P2J server's port.
|
| 1002 |
* @param chost
|
| 1003 |
* The P2J server's host.
|
| 1004 |
* @param calias
|
| 1005 |
* The certificate alias for the P2J server.
|
| 1006 |
* @param uuid
|
| 1007 |
* Unique identifier for this request; must be known by the target P2J server.
|
| 1008 |
*/
|
| 1009 |
int launchP2JClient(char cport[], char chost[], char calias[], char cuuid[], char cmisc[]) |
| 1010 |
{
|
| 1011 |
/* variables to use shared memory in child process to work with JVM */
|
| 1012 |
pid_t child_pid; |
| 1013 |
int status = 0; |
| 1014 |
int shmid;
|
| 1015 |
|
| 1016 |
/* prepare shared memory to use inside child process, permissions: rw-r----- */
|
| 1017 |
shmid = shmget(IPC_PRIVATE, SHM_SIZE, SHM_PERM_RW_R_NO | IPC_CREAT); |
| 1018 |
if (shmid < 0) |
| 1019 |
{
|
| 1020 |
/* On shmget() fails */
|
| 1021 |
logMessage("Failure in shared memory allocation request.");
|
| 1022 |
exit(audit(ERR_SPAWN_SHMEM_GET, "shmget"));
|
| 1023 |
} |
| 1024 |
|
| 1025 |
/* starting child process to work with JVM */
|
| 1026 |
child_pid = fork(); |
| 1027 |
if (child_pid == 0) |
| 1028 |
{
|
| 1029 |
// get shared memory region from starting with full access
|
| 1030 |
void * sh_data = shmat(shmid, (void *)0, 0); |
| 1031 |
if (sh_data == (void *)-1) |
| 1032 |
{
|
| 1033 |
exit(audit(ERR_SPAWN_SHMEM_AT, "shmat in child"));
|
| 1034 |
} |
| 1035 |
/* alias to original address */
|
| 1036 |
void * sh_data_orig = sh_data;
|
| 1037 |
|
| 1038 |
/* inside child process */
|
| 1039 |
JavaVM *jvm; |
| 1040 |
JNIEnv *env; |
| 1041 |
|
| 1042 |
int nopts;
|
| 1043 |
|
| 1044 |
JavaVMOption options[8];
|
| 1045 |
options[0].optionString = "-Djava.class.path=./p2j.jar:./slf4j-api.jar:./aspectjrt.jar:."; |
| 1046 |
options[1].optionString = "-Djava.lib.path=."; |
| 1047 |
options[2].optionString = "-Xmx512m"; |
| 1048 |
options[3].optionString = "-Djava.awt.headless=true"; |
| 1049 |
options[4].optionString = "-Djava.compiler=NONE"; |
| 1050 |
nopts = 5;
|
| 1051 |
|
| 1052 |
if (DEBUG_JVM)
|
| 1053 |
{
|
| 1054 |
options[5].optionString = "-Xdebug"; |
| 1055 |
options[6].optionString = "-Xnoagent"; |
| 1056 |
options[7].optionString = "-Xrunjdwp:transport=dt_socket,address=2999,server=y,suspend=y"; |
| 1057 |
nopts = 8;
|
| 1058 |
} |
| 1059 |
|
| 1060 |
JavaVMInitArgs vm_args; /* VM initialization arguments */
|
| 1061 |
vm_args.version = JNI_VERSION_1_6; |
| 1062 |
vm_args.nOptions = nopts; |
| 1063 |
vm_args.options = options; |
| 1064 |
vm_args.ignoreUnrecognized = 0;
|
| 1065 |
|
| 1066 |
jint err = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
|
| 1067 |
if (err != JNI_OK)
|
| 1068 |
{
|
| 1069 |
exit(audit(ERR_JNI_JVM_CREATE, "JNI_CreateJavaVM"));
|
| 1070 |
} |
| 1071 |
|
| 1072 |
jclass cls = (*env)->FindClass(env, "com/goldencode/p2j/main/NativeSecureConnection");
|
| 1073 |
if (cls == NULL) |
| 1074 |
{
|
| 1075 |
(*jvm)->DestroyJavaVM(jvm); |
| 1076 |
|
| 1077 |
exit(audit(ERR_JNI_P2J_ENTRY_POINT_CLASS, "FindClass(NativeSecureConnection)"));
|
| 1078 |
} |
| 1079 |
|
| 1080 |
char *signature =
|
| 1081 |
"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;";
|
| 1082 |
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "command", signature);
|
| 1083 |
if (mid == NULL) |
| 1084 |
{
|
| 1085 |
(*jvm)->DestroyJavaVM(jvm); |
| 1086 |
|
| 1087 |
exit(audit(ERR_JNI_P2J_ENTRY_POINT_METHOD, "GetStaticMethodID(command)"));
|
| 1088 |
} |
| 1089 |
|
| 1090 |
int port = atoi(cport);
|
| 1091 |
|
| 1092 |
jstring host = (*env)->NewStringUTF(env, chost); |
| 1093 |
if (host == NULL) |
| 1094 |
{
|
| 1095 |
(*jvm)->DestroyJavaVM(jvm); |
| 1096 |
|
| 1097 |
exit(audit(ERR_JNI_REMOTE_SERVER_HOST, "NewStringUTF(host)"));
|
| 1098 |
} |
| 1099 |
|
| 1100 |
jstring alias = (*env)->NewStringUTF(env, calias); |
| 1101 |
if (alias == NULL) |
| 1102 |
{
|
| 1103 |
(*jvm)->DestroyJavaVM(jvm); |
| 1104 |
|
| 1105 |
exit(audit(ERR_JNI_REMOTE_SERVER_ALIAS, "NewStringUTF(alias)"));
|
| 1106 |
} |
| 1107 |
|
| 1108 |
jstring uuid = (*env)->NewStringUTF(env, cuuid); |
| 1109 |
if (uuid == NULL) |
| 1110 |
{
|
| 1111 |
(*jvm)->DestroyJavaVM(jvm); |
| 1112 |
|
| 1113 |
exit(audit(ERR_JNI_REMOTE_SERVER_UUID, "NewStringUTF(uuid)"));
|
| 1114 |
} |
| 1115 |
|
| 1116 |
jstring misc = (*env)->NewStringUTF(env, cmisc); |
| 1117 |
if (misc == NULL) |
| 1118 |
{
|
| 1119 |
(*jvm)->DestroyJavaVM(jvm); |
| 1120 |
|
| 1121 |
exit(audit(ERR_JNI_REMOTE_SERVER_MISC, "NewStringUTF(misc)"));
|
| 1122 |
} |
| 1123 |
|
| 1124 |
jobjectArray command = (*env)->CallStaticObjectMethod(env, (jclass) cls, (jmethodID) mid, |
| 1125 |
port, host, alias, uuid, misc); |
| 1126 |
|
| 1127 |
if (command == NULL) |
| 1128 |
{
|
| 1129 |
(*jvm)->DestroyJavaVM(jvm); |
| 1130 |
|
| 1131 |
exit(audit(ERR_JNI_REMOTE_COMMAND, "CallStaticObjectMethod(command)"));
|
| 1132 |
} |
| 1133 |
|
| 1134 |
/* extract the arguments */
|
| 1135 |
jsize jlength = (*env)->GetArrayLength(env, (jarray) command); |
| 1136 |
/* store arguments length */
|
| 1137 |
memcpy(sh_data, &jlength, sizeof(jsize));
|
| 1138 |
/* advance pointer to next memory address to write */
|
| 1139 |
sh_data += sizeof(jsize);
|
| 1140 |
|
| 1141 |
/* store all arguments coming from JVM in shared memory */
|
| 1142 |
int i;
|
| 1143 |
for (i = 0; i < (int) jlength; i++) |
| 1144 |
{
|
| 1145 |
jstring jarg = (jstring) (*env)->GetObjectArrayElement(env, command, (jsize) i); |
| 1146 |
jsize argLength = (*env)->GetStringUTFLength(env, jarg); |
| 1147 |
const char *narg = (*env)->GetStringUTFChars(env, jarg, 0); |
| 1148 |
|
| 1149 |
if (narg != NULL) |
| 1150 |
{
|
| 1151 |
sprintf(message_buffer, "The argument number %d is: %s.", i, narg);
|
| 1152 |
logMessage(message_buffer); |
| 1153 |
|
| 1154 |
int nlength = (int) argLength + 1; |
| 1155 |
/* argument length does not include terminated null char */
|
| 1156 |
memcpy(sh_data, &argLength, sizeof(jsize));
|
| 1157 |
/* advance pointer to next memory address to write */
|
| 1158 |
sh_data += sizeof(jsize);
|
| 1159 |
memset(sh_data, '\0', nlength);
|
| 1160 |
strcpy(sh_data, narg); |
| 1161 |
/* advance pointer to next memory address to write */
|
| 1162 |
sh_data += nlength; |
| 1163 |
} |
| 1164 |
|
| 1165 |
(*env)->ReleaseStringUTFChars(env, jarg, narg); |
| 1166 |
} |
| 1167 |
|
| 1168 |
/* detach shared memory*/
|
| 1169 |
if (shmdt(sh_data_orig) == -1) |
| 1170 |
{
|
| 1171 |
exit(audit(ERR_SPAWN_SHMEM_DT, "shmdt in child"));
|
| 1172 |
} |
| 1173 |
|
| 1174 |
err = (*jvm)->DestroyJavaVM(jvm); |
| 1175 |
if (err < 0) |
| 1176 |
{
|
| 1177 |
exit(audit(ERR_JNI_JVM_DESTROY, "DestroyJavaVM"));
|
| 1178 |
} |
| 1179 |
|
| 1180 |
/* terminate child process */
|
| 1181 |
exit(EXIT_SUCCESS); |
| 1182 |
} |
| 1183 |
else if (child_pid < 0) |
| 1184 |
{
|
| 1185 |
/* On fork() fails */
|
| 1186 |
logMessage("Failure in child process creation to process JVM.");
|
| 1187 |
exit(audit(ERR_SPAWN_FORK_SHMEM, "fork for JVM"));
|
| 1188 |
} |
| 1189 |
|
| 1190 |
/* Wait for child to exit, no need to semaphore usage */
|
| 1191 |
wait(&status); |
| 1192 |
|
| 1193 |
/* process shared memory results */
|
| 1194 |
|
| 1195 |
/* get shared memory region */
|
| 1196 |
void * sh_data = shmat(shmid, (void *)0, SHM_RDONLY); |
| 1197 |
if (sh_data == (void *)-1) |
| 1198 |
{
|
| 1199 |
exit(audit(ERR_SPAWN_SHMEM_AT, "shmat in parent"));
|
| 1200 |
} |
| 1201 |
/* alias to original address */
|
| 1202 |
void * sh_data_orig = sh_data;
|
| 1203 |
|
| 1204 |
int length = 0; |
| 1205 |
memcpy(&length, sh_data, sizeof(jsize));
|
| 1206 |
sh_data += sizeof(jsize);
|
| 1207 |
|
| 1208 |
/* make it 1-based, so it follows the structure of the command line arguments
|
| 1209 |
(0 is program name); also, one more position is needed for execvp, which requires for the
|
| 1210 |
arguments to be null-terminated. */
|
| 1211 |
char **arguments = (char**) malloc((length + 2 + output_to_file_opt_found) * sizeof(char*)); |
| 1212 |
arguments[0] = NULL; |
| 1213 |
arguments[length + 1 + output_to_file_opt_found] = NULL; |
| 1214 |
|
| 1215 |
if (output_to_file_opt_found == 1) |
| 1216 |
{
|
| 1217 |
int size = (strlen(output_file_name) + 1) * sizeof(char); |
| 1218 |
arguments[length + 1] = (char*) malloc(size); |
| 1219 |
memset(arguments[length + 1], '\0', size); |
| 1220 |
strcpy(arguments[length + 1], output_file_name);
|
| 1221 |
} |
| 1222 |
|
| 1223 |
/* extract argumants from shared memory*/
|
| 1224 |
int j;
|
| 1225 |
for (j = 0; j < length; j++) |
| 1226 |
{
|
| 1227 |
int argLen = 0; |
| 1228 |
|
| 1229 |
/* argument length does not include terminated null char */
|
| 1230 |
memcpy(&argLen, sh_data, sizeof(jsize));
|
| 1231 |
sh_data += sizeof(jsize);
|
| 1232 |
|
| 1233 |
int nlen = (int) argLen + 1; |
| 1234 |
arguments[j + 1] = (char*) malloc(nlen * sizeof(char)); |
| 1235 |
memset(arguments[j + 1], '\0', nlen); |
| 1236 |
strcpy(arguments[j + 1], sh_data);
|
| 1237 |
sh_data += nlen; |
| 1238 |
} |
| 1239 |
|
| 1240 |
/* detach shared memory*/
|
| 1241 |
if (shmdt(sh_data_orig) == -1) |
| 1242 |
{
|
| 1243 |
exit(audit(ERR_SPAWN_SHMEM_DT, "shmdt in parent"));
|
| 1244 |
} |
| 1245 |
|
| 1246 |
/* release OS resources */
|
| 1247 |
if (shmctl(shmid, IPC_RMID, NULL) == -1) |
| 1248 |
{
|
| 1249 |
exit(audit(ERR_SPAWN_SHMEM_FREE, "shmctl in parent to release shared memory"));
|
| 1250 |
} |
| 1251 |
|
| 1252 |
if (length < MIN_ARGS_COMMAND)
|
| 1253 |
{
|
| 1254 |
help("");
|
| 1255 |
exit(ERR_SPAWN_NO_ARGS); |
| 1256 |
} |
| 1257 |
else
|
| 1258 |
{
|
| 1259 |
/* Check user account credentials */
|
| 1260 |
int result = check_user(arguments[1], NULL); |
| 1261 |
|
| 1262 |
switch (result)
|
| 1263 |
{
|
| 1264 |
case AUTH_OK:
|
| 1265 |
sprintf(message_buffer, |
| 1266 |
"Starting with argv[MIN_ARGS_COMMAND]: (%s), argv[MIN_ARGS_COMMAND - 1]: (%s).",
|
| 1267 |
arguments[MIN_ARGS_COMMAND], |
| 1268 |
arguments[MIN_ARGS_COMMAND - 1]);
|
| 1269 |
logMessage(message_buffer); |
| 1270 |
result = spawn(arguments[MIN_ARGS_COMMAND], |
| 1271 |
&arguments[MIN_ARGS_COMMAND], |
| 1272 |
arguments[MIN_ARGS_COMMAND - 1]);
|
| 1273 |
break;
|
| 1274 |
case AUTH_ERR_USER:
|
| 1275 |
fputs("SEVERE: Invalid user name.\n", stderr);
|
| 1276 |
break;
|
| 1277 |
case AUTH_ERR_PASSWORD:
|
| 1278 |
fputs("SEVERE: Invalid password.\n", stderr);
|
| 1279 |
break;
|
| 1280 |
case AUTH_ERR_ROOT:
|
| 1281 |
fputs("SEVERE: User root not allowed.\n", stderr);
|
| 1282 |
break;
|
| 1283 |
case AUTH_ERR_NULL_ACCOUNT_PASSWORD:
|
| 1284 |
fputs("SEVERE: No password set for user account.\n", stderr);
|
| 1285 |
break;
|
| 1286 |
case AUTH_ERR_CRYPT:
|
| 1287 |
fputs("SEVERE: Password encryption error.\n", stderr);
|
| 1288 |
break;
|
| 1289 |
} |
| 1290 |
sprintf(message_buffer, "Spawn result code: (%d).", result);
|
| 1291 |
logMessage(message_buffer); |
| 1292 |
return result;
|
| 1293 |
} |
| 1294 |
|
| 1295 |
return EXIT_SUCCESS;
|
| 1296 |
} |
| 1297 |
|
| 1298 |
/**
|
| 1299 |
* Replace a placeholder within a string.
|
| 1300 |
*
|
| 1301 |
* @param string
|
| 1302 |
* Original string.
|
| 1303 |
* @param placeholder
|
| 1304 |
* Placeholder string.
|
| 1305 |
* @param value
|
| 1306 |
* Replacement string value.
|
| 1307 |
*
|
| 1308 |
* @return The string after placeholder was replaced.
|
| 1309 |
* If the placeholder is not found returns original string.
|
| 1310 |
*/
|
| 1311 |
char* replace_str(char* string, const char* placeholder, char* value) |
| 1312 |
{
|
| 1313 |
static char buffer[MAX_FILENAME]; |
| 1314 |
int len;
|
| 1315 |
char *p;
|
| 1316 |
|
| 1317 |
p = strstr(string, placeholder); |
| 1318 |
|
| 1319 |
if (!p)
|
| 1320 |
{
|
| 1321 |
// placeholder not found
|
| 1322 |
return string;
|
| 1323 |
} |
| 1324 |
|
| 1325 |
len = p - string; |
| 1326 |
strncpy(buffer, string, len); |
| 1327 |
buffer[len] = 0;
|
| 1328 |
|
| 1329 |
sprintf(buffer + len, "%s%s", value, p + strlen(placeholder));
|
| 1330 |
|
| 1331 |
return buffer;
|
| 1332 |
} |
| 1333 |
|
| 1334 |
/**
|
| 1335 |
* Redirect STDOUT to an output file.
|
| 1336 |
*/
|
| 1337 |
void stdout_redirect()
|
| 1338 |
{
|
| 1339 |
int output_fd;
|
| 1340 |
|
| 1341 |
/* making output file name with PID */
|
| 1342 |
sprintf(log_file_name, "spawn_native_%u.log", getpid());
|
| 1343 |
|
| 1344 |
// Open file
|
| 1345 |
output_fd = open(log_file_name, O_WRONLY | O_CREAT | O_APPEND, 0644);
|
| 1346 |
|
| 1347 |
if (output_fd == -1) |
| 1348 |
{
|
| 1349 |
fprintf(stderr, "Cannot open file %s\n", log_file_name);
|
| 1350 |
} |
| 1351 |
else
|
| 1352 |
{
|
| 1353 |
/* Redirect STDOUT to file */
|
| 1354 |
if (dup2(output_fd, STDOUT_FILENO) == -1) |
| 1355 |
{
|
| 1356 |
close(output_fd); |
| 1357 |
fprintf(stderr, "Cannot redirect STDOUT to file %s\n", log_file_name);
|
| 1358 |
} |
| 1359 |
} |
| 1360 |
} |
| 1361 |
|
| 1362 |
/**
|
| 1363 |
* Main function which allows both password and P2J server-style authentication.
|
| 1364 |
* <p>
|
| 1365 |
* For P2J server-style authentiation, the following files need to exist in current directory:
|
| 1366 |
* - p2j.jar, which will automatically be added to classpath
|
| 1367 |
* - srv-certs.store, a key store with the certificates for the valid P2J server which can invoke
|
| 1368 |
* this utility.
|
| 1369 |
*
|
| 1370 |
* Link option: -lm -lcrypt -lpam -ljvm
|
| 1371 |
* <p>
|
| 1372 |
* After build do the following:
|
| 1373 |
* $ sudo chown root:root spawn
|
| 1374 |
* $ sudo chmod 4755 spawn
|
| 1375 |
* $ sudo chmod 0440 srv-certs.store
|
| 1376 |
* $ sudo chmod 0440 p2j.jar
|
| 1377 |
*
|
| 1378 |
* @param argc
|
| 1379 |
* Number of command line arguments.
|
| 1380 |
* @param argv
|
| 1381 |
* Command line arguments.
|
| 1382 |
*
|
| 1383 |
* @return Exit status.
|
| 1384 |
*/
|
| 1385 |
int main(int argc, char *argv[]) |
| 1386 |
{
|
| 1387 |
int result, i;
|
| 1388 |
char pid[32]; |
| 1389 |
|
| 1390 |
/* convert pid to string */
|
| 1391 |
sprintf(pid, "%u", getpid());
|
| 1392 |
sprintf(message_buffer, "Spawn process started with pid %s.", pid);
|
| 1393 |
logMessage(message_buffer); |
| 1394 |
|
| 1395 |
/* Init output file name buffer */
|
| 1396 |
output_file_name[0] = 0; |
| 1397 |
|
| 1398 |
/* Check for root permissions */
|
| 1399 |
if (check_root(argv[0], 04755)) |
| 1400 |
{
|
| 1401 |
fputs("SEVERE: No root access permissions.\n", stderr);
|
| 1402 |
exit(ERR_SPAWN_NO_ROOT_PERMISSION); |
| 1403 |
} |
| 1404 |
|
| 1405 |
/* Print a short help */
|
| 1406 |
if (argc == 1) |
| 1407 |
{
|
| 1408 |
help(argv[0]);
|
| 1409 |
exit(ERR_SPAWN_NO_ARGS); |
| 1410 |
} |
| 1411 |
|
| 1412 |
char *smode = argv[1]; |
| 1413 |
if (strcmp(smode, "1") == 0 || strcmp(smode, "2") == 0) |
| 1414 |
{
|
| 1415 |
int mode1 = strcmp(smode, "1"); |
| 1416 |
int mode2 = strcmp(smode, "2"); |
| 1417 |
|
| 1418 |
if ((mode1 == 0 && argc < MIN_ARGS_PASSWORD) || (mode2 == 0 && argc < MIN_ARGS_CREDS)) |
| 1419 |
{
|
| 1420 |
help(argv[0]);
|
| 1421 |
exit(ERR_SPAWN_NO_ARGS); |
| 1422 |
} |
| 1423 |
|
| 1424 |
/* Ask for password */
|
| 1425 |
size_t len; |
| 1426 |
char *psw = password(&len);
|
| 1427 |
|
| 1428 |
if (psw == NULL) |
| 1429 |
{
|
| 1430 |
fputs("SEVERE: No password.\n", stderr);
|
| 1431 |
exit(ERR_SPAWN_NO_PASSWORD); |
| 1432 |
} |
| 1433 |
|
| 1434 |
/* Check user account credentials */
|
| 1435 |
result = check_user(argv[2], psw);
|
| 1436 |
|
| 1437 |
/* Password is no longer needed at this point, zero it so it doesn't stay in memory. */
|
| 1438 |
zap(&psw, &len); |
| 1439 |
|
| 1440 |
switch (result)
|
| 1441 |
{
|
| 1442 |
case AUTH_OK:
|
| 1443 |
if (mode1 == 0) |
| 1444 |
{
|
| 1445 |
// we need to consider possible one more extra parameter before java arguments
|
| 1446 |
sprintf(message_buffer, |
| 1447 |
"Spawn cmd (%s) in workdir (%s).",
|
| 1448 |
argv[MIN_ARGS_PASSWORD], |
| 1449 |
argv[MIN_ARGS_PASSWORD - 1]);
|
| 1450 |
logMessage(message_buffer); |
| 1451 |
result = spawn(argv[MIN_ARGS_PASSWORD], |
| 1452 |
&argv[MIN_ARGS_PASSWORD], |
| 1453 |
argv[MIN_ARGS_PASSWORD - 1]);
|
| 1454 |
} |
| 1455 |
break;
|
| 1456 |
case AUTH_ERR_USER:
|
| 1457 |
fputs("Invalid user name.\n", stderr);
|
| 1458 |
break;
|
| 1459 |
case AUTH_ERR_PASSWORD:
|
| 1460 |
fputs("Invalid password.\n", stderr);
|
| 1461 |
break;
|
| 1462 |
case AUTH_ERR_ROOT:
|
| 1463 |
fputs("User root not allowed.\n", stderr);
|
| 1464 |
break;
|
| 1465 |
case AUTH_ERR_NULL_ACCOUNT_PASSWORD:
|
| 1466 |
fputs("No password set for user account.\n", stderr);
|
| 1467 |
break;
|
| 1468 |
case AUTH_ERR_CRYPT:
|
| 1469 |
fputs("Password encryption error.\n", stderr);
|
| 1470 |
break;
|
| 1471 |
} |
| 1472 |
sprintf(message_buffer, "Spawn result code: (%d).", result);
|
| 1473 |
logMessage(message_buffer); |
| 1474 |
} |
| 1475 |
else if (strcmp(smode, "0") == 0) |
| 1476 |
{
|
| 1477 |
if (check_root(KEY_STORE, 00550)) |
| 1478 |
{
|
| 1479 |
fputs("No root access permissions for srv-certs.store.\n", stderr);
|
| 1480 |
exit(ERR_SPAWN_NO_ROOT_PERMISSION); |
| 1481 |
} |
| 1482 |
if (check_root(P2J_JAR, 00550)) |
| 1483 |
{
|
| 1484 |
fputs("No root access permissions for p2j.jar.\n", stderr);
|
| 1485 |
exit(ERR_SPAWN_NO_ROOT_PERMISSION); |
| 1486 |
} |
| 1487 |
|
| 1488 |
if (argc < MIN_ARGS_NO_PASSWORD)
|
| 1489 |
{
|
| 1490 |
help(argv[0]);
|
| 1491 |
exit(ERR_SPAWN_NO_ARGS); |
| 1492 |
} |
| 1493 |
|
| 1494 |
/* Search for optional parameters */
|
| 1495 |
for (i = MIN_ARGS_NO_PASSWORD; i < argc; i++)
|
| 1496 |
{
|
| 1497 |
/* -O <outputToFile> */
|
| 1498 |
if ((strcmp("-O", argv[i]) == 0) && (++i < argc)) |
| 1499 |
{
|
| 1500 |
strcpy(output_file_name, argv[i]); |
| 1501 |
output_to_file_opt_found = 1;
|
| 1502 |
} |
| 1503 |
|
| 1504 |
/* -L switch to mmake native errors logging */
|
| 1505 |
if (strcmp("-L", argv[i]) == 0) |
| 1506 |
{
|
| 1507 |
native_log_found = 1;
|
| 1508 |
} |
| 1509 |
} |
| 1510 |
|
| 1511 |
char* misc = (argc < 7) ? "" : argv[6]; |
| 1512 |
// when the mode is 0 the extra debug option is located after 2-5 parameters so no need
|
| 1513 |
// to shift indexes for parameters in call below, it is safe
|
| 1514 |
result = launchP2JClient(argv[2], argv[3], argv[4], argv[5], misc); |
| 1515 |
} |
| 1516 |
else
|
| 1517 |
{
|
| 1518 |
help(argv[0]);
|
| 1519 |
exit(ERR_SPAWN_NO_ARGS); |
| 1520 |
} |
| 1521 |
|
| 1522 |
exit(result); |
| 1523 |
} |