mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Squashed 'osx/updater/' content from commit a558402
git-subtree-dir: osx/updater git-subtree-split: a5584021435fc74a1e5da447524b37dcf7311bbc
This commit is contained in:
commit
8934a11dbf
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
13
README.md
Normal file
13
README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
The qTox OS X updater is a mix of objective C and Go compiled as static binaries used do effortless updates in the background.
|
||||
|
||||
It uses Objective C to access Apples own security framework and call some long dead APIs in order to give the statically linked go updater permissions to install the latest build without prompting the user for every file.
|
||||
|
||||
* Release commits: ``https://github.com/Tox/qTox_updater``
|
||||
* Development commits: ``https://github.mit.edu/sean-2/updater``
|
||||
|
||||
Compiling:
|
||||
|
||||
* ```clang qtox_sudo.m -framework corefoundation -framework security -framework cocoa -Os -o qtox_sudo```
|
||||
* ```go build updater.go```
|
||||
|
||||
(Starting with this commit all commits will be signed with [this key](http://pgp.mit.edu/pks/lookup?op=get&search=0x13D2043169D25DF4).)
|
273
qtox_sudo.m
Normal file
273
qtox_sudo.m
Normal file
|
@ -0,0 +1,273 @@
|
|||
// Modifications listed here GPLv3: https://gist.githubusercontent.com/stqism/2e82352026915f8f6ab3/raw/fca6f6f16fb8d61a64b6053dacf50c3433c2e0af/gistfile1.txt
|
||||
//
|
||||
// Copyright 2009 Performant Design, LLC. All rights reserved.
|
||||
// Copyright (C) 2014 Tox Foundation
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify it
|
||||
// under the terms of the GNU General Public License as published by the Free
|
||||
// Software Foundation, either version 3 of the License, or (at your option)
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
// more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License along with
|
||||
// this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <Security/Authorization.h>
|
||||
#include <Security/AuthorizationTags.h>
|
||||
|
||||
char *addFileToPath(const char *path, const char *filename) {
|
||||
char *outbuf;
|
||||
char *lc;
|
||||
|
||||
lc = (char *)path + strlen(path) - 1;
|
||||
|
||||
if (lc < path || *lc != '/') {
|
||||
lc = NULL;
|
||||
}
|
||||
|
||||
while (*filename == '/') {
|
||||
filename++;
|
||||
}
|
||||
|
||||
outbuf = malloc(strlen(path) + strlen(filename) + 1 + (lc == NULL ? 1 : 0));
|
||||
|
||||
sprintf(outbuf, "%s%s%s", path, (lc == NULL) ? "/" : "", filename);
|
||||
|
||||
return outbuf;
|
||||
}
|
||||
|
||||
int isExecFile(const char *name) {
|
||||
struct stat s;
|
||||
|
||||
return (!access(name, X_OK) && !stat(name, &s) && S_ISREG(s.st_mode));
|
||||
}
|
||||
|
||||
char *which(const char *filename)
|
||||
{
|
||||
char *path, *p, *n;
|
||||
|
||||
path = getenv("PATH");
|
||||
|
||||
if (!path) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
p = path = strdup(path);
|
||||
|
||||
while (p) {
|
||||
n = strchr(p, ':');
|
||||
|
||||
if (n) {
|
||||
*n++ = '\0';
|
||||
}
|
||||
|
||||
if (*p != '\0') {
|
||||
p = addFileToPath(p, filename);
|
||||
|
||||
if (isExecFile(p)) {
|
||||
free(path);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
free(p);
|
||||
}
|
||||
|
||||
p = n;
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cocoaSudo(char *executable, char *commandArgs[], char *icon, char *prompt) {
|
||||
int retVal = 1;
|
||||
OSStatus status;
|
||||
AuthorizationRef authRef;
|
||||
|
||||
AuthorizationItem right = {kAuthorizationRightExecute, 0, NULL, 0};
|
||||
AuthorizationRights rightSet = {1, &right};
|
||||
|
||||
AuthorizationEnvironment myAuthorizationEnvironment;
|
||||
AuthorizationItem kAuthEnv[2];
|
||||
myAuthorizationEnvironment.items = kAuthEnv;
|
||||
|
||||
AuthorizationFlags flags = kAuthorizationFlagDefaults;
|
||||
|
||||
|
||||
if (prompt && icon) {
|
||||
kAuthEnv[0].name = kAuthorizationEnvironmentPrompt;
|
||||
kAuthEnv[0].valueLength = strlen(prompt);
|
||||
kAuthEnv[0].value = prompt;
|
||||
kAuthEnv[0].flags = 0;
|
||||
|
||||
kAuthEnv[1].name = kAuthorizationEnvironmentIcon;
|
||||
kAuthEnv[1].valueLength = strlen(icon);
|
||||
kAuthEnv[1].value = icon;
|
||||
kAuthEnv[1].flags = 0;
|
||||
|
||||
myAuthorizationEnvironment.count = 2;
|
||||
}
|
||||
else if (prompt) {
|
||||
kAuthEnv[0].name = kAuthorizationEnvironmentPrompt;
|
||||
kAuthEnv[0].valueLength = strlen(prompt);
|
||||
kAuthEnv[0].value = prompt;
|
||||
kAuthEnv[0].flags = 0;
|
||||
|
||||
myAuthorizationEnvironment.count = 1;
|
||||
}
|
||||
else if (icon) {
|
||||
kAuthEnv[0].name = kAuthorizationEnvironmentIcon;
|
||||
kAuthEnv[0].valueLength = strlen(icon);
|
||||
kAuthEnv[0].value = icon;
|
||||
kAuthEnv[0].flags = 0;
|
||||
|
||||
myAuthorizationEnvironment.count = 1;
|
||||
}
|
||||
else {
|
||||
myAuthorizationEnvironment.count = 0;
|
||||
}
|
||||
|
||||
status = AuthorizationCreate(NULL, &myAuthorizationEnvironment, flags, &authRef);
|
||||
|
||||
if (status != errAuthorizationSuccess) {
|
||||
NSLog(@"Could not create authorization reference object.");
|
||||
status = errAuthorizationBadAddress;
|
||||
}
|
||||
else {
|
||||
flags = kAuthorizationFlagDefaults |
|
||||
kAuthorizationFlagInteractionAllowed |
|
||||
kAuthorizationFlagPreAuthorize |
|
||||
kAuthorizationFlagExtendRights;
|
||||
|
||||
status = AuthorizationCopyRights(authRef, &rightSet, &myAuthorizationEnvironment, flags, NULL);
|
||||
}
|
||||
|
||||
if (status == errAuthorizationSuccess) {
|
||||
FILE *ioPipe;
|
||||
char buffer[1024];
|
||||
int bytesRead;
|
||||
|
||||
flags = kAuthorizationFlagDefaults;
|
||||
status = AuthorizationExecuteWithPrivileges(authRef, executable, flags, commandArgs, &ioPipe);
|
||||
|
||||
/* Just pipe processes' stdout to our stdout for now; hopefully can add stdin pipe later as well */
|
||||
|
||||
for (;;) {
|
||||
bytesRead = fread(buffer, sizeof(char), 1024, ioPipe);
|
||||
|
||||
if (bytesRead < 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
write(STDOUT_FILENO, buffer, bytesRead * sizeof(char));
|
||||
}
|
||||
|
||||
pid_t pid;
|
||||
int pidStatus;
|
||||
|
||||
do {
|
||||
pid = wait(&pidStatus);
|
||||
}
|
||||
while (pid != -1);
|
||||
|
||||
if (status == errAuthorizationSuccess) {
|
||||
retVal = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
AuthorizationFree(authRef, kAuthorizationFlagDestroyRights);
|
||||
authRef = NULL;
|
||||
|
||||
if (status != errAuthorizationCanceled) {
|
||||
// pre-auth failed
|
||||
NSLog(@"Pre-auth failed");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
void usage(char *appNameFull) {
|
||||
char *appName = strrchr(appNameFull, '/');
|
||||
|
||||
if (appName == NULL) {
|
||||
appName = appNameFull;
|
||||
}
|
||||
else {
|
||||
appName++;
|
||||
}
|
||||
|
||||
fprintf(stderr, "usage: %s command\n", appName);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
int retVal = 1;
|
||||
int programArgsStartAt = 1;
|
||||
char *icon = NULL;
|
||||
char *prompt = NULL;
|
||||
|
||||
if (programArgsStartAt >= argc) {
|
||||
usage(argv[0]);
|
||||
}
|
||||
else {
|
||||
char *executable;
|
||||
|
||||
if (strchr(argv[programArgsStartAt], '/')) {
|
||||
executable = isExecFile(argv[programArgsStartAt]) ? strdup(argv[programArgsStartAt]) : NULL;
|
||||
}
|
||||
else {
|
||||
executable = which(argv[programArgsStartAt]);
|
||||
}
|
||||
|
||||
if (executable) {
|
||||
char **commandArgs = malloc((argc - programArgsStartAt) * sizeof(char**));
|
||||
|
||||
memcpy(commandArgs, argv + programArgsStartAt + 1, (argc - programArgsStartAt - 1) * sizeof(char**));
|
||||
|
||||
commandArgs[argc - programArgsStartAt - 1] = NULL;
|
||||
|
||||
retVal = cocoaSudo(executable, commandArgs, icon, prompt);
|
||||
|
||||
free(commandArgs);
|
||||
free(executable);
|
||||
}
|
||||
else {
|
||||
fprintf(stderr, "Unable to find %s\n", argv[programArgsStartAt]);
|
||||
|
||||
usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (prompt) {
|
||||
free(prompt);
|
||||
}
|
||||
|
||||
[pool release];
|
||||
|
||||
return retVal;
|
||||
}
|
133
updater.go
Normal file
133
updater.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"syscall"
|
||||
|
||||
"bitbucket.org/kardianos/osext"
|
||||
)
|
||||
|
||||
var custom_user string
|
||||
|
||||
func fs_type(path string) int {
|
||||
//name := "FileOrDir"
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return -1
|
||||
}
|
||||
defer f.Close()
|
||||
fi, err := f.Stat()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return -1
|
||||
}
|
||||
switch mode := fi.Mode(); {
|
||||
case mode.IsDir():
|
||||
return 0
|
||||
case mode.IsRegular():
|
||||
return 1
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func install(path string, pathlen int) int {
|
||||
files, _ := ioutil.ReadDir(path)
|
||||
|
||||
for _, file := range files {
|
||||
if fs_type(path+file.Name()) == 1 {
|
||||
|
||||
addpath := ""
|
||||
if len(path) != pathlen {
|
||||
addpath = path[pathlen:len(path)]
|
||||
}
|
||||
|
||||
fmt.Print("Installing: ")
|
||||
fmt.Println("/Applications/qtox.app/Contents/" + addpath + file.Name())
|
||||
if _, err := os.Stat("/Applications/qtox.app/Contents/" + file.Name()); os.IsNotExist(err) {
|
||||
newfile := exec.Command("/usr/libexec/authopen", "-c", "-x", "-m", "drwxrwxr-x+", "/Applications/qtox.app/Contents/"+addpath+file.Name())
|
||||
newfile.Run()
|
||||
}
|
||||
|
||||
cat := exec.Command("/bin/cat", path+file.Name())
|
||||
|
||||
auth := exec.Command("/usr/libexec/authopen", "-w", "/Applications/qtox.app/Contents/"+addpath+file.Name())
|
||||
auth.Stdin, _ = cat.StdoutPipe()
|
||||
auth.Stdout = os.Stdout
|
||||
auth.Stderr = os.Stderr
|
||||
_ = auth.Start()
|
||||
_ = cat.Run()
|
||||
_ = auth.Wait()
|
||||
|
||||
} else {
|
||||
install(path+file.Name()+"/", pathlen)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
syscall.Setuid(0)
|
||||
usr, e := user.Current()
|
||||
if e != nil {
|
||||
log.Fatal(e)
|
||||
}
|
||||
|
||||
CHECK:
|
||||
if usr.Name != "System Administrator" {
|
||||
fmt.Println("Not running as root, relaunching")
|
||||
|
||||
appdir, _ := osext.Executable()
|
||||
appdir_len := len(appdir)
|
||||
sudo_path := appdir[0:(appdir_len-7)] + "qtox_sudo" //qtox_sudo is a fork of cocoasudo with all of its flags and other features stripped out
|
||||
|
||||
if _, err := os.Stat(sudo_path); os.IsNotExist(err) {
|
||||
fmt.Println("Error: No qtox_sudo binary installed, falling back")
|
||||
custom_user = usr.Name
|
||||
usr.Name = "System Administrator"
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
relaunch := exec.Command(sudo_path, appdir, usr.Name)
|
||||
relaunch.Stdout = os.Stdout
|
||||
relaunch.Stderr = os.Stderr
|
||||
relaunch.Run()
|
||||
return
|
||||
|
||||
} else {
|
||||
|
||||
if len(os.Args) > 1 || custom_user != "" {
|
||||
|
||||
if custom_user == "" {
|
||||
custom_user = os.Args[1]
|
||||
}
|
||||
|
||||
update_dir := "/Users/" + custom_user + "/Library/Preferences/tox/update/"
|
||||
if _, err := os.Stat(update_dir); os.IsNotExist(err) {
|
||||
fmt.Println("Error: No update folder, is check for updates enabled?")
|
||||
return
|
||||
}
|
||||
fmt.Println("qTox Updater")
|
||||
|
||||
killqtox := exec.Command("/usr/bin/killall", "qtox")
|
||||
_ = killqtox.Run()
|
||||
|
||||
install(update_dir, len(update_dir))
|
||||
|
||||
os.RemoveAll(update_dir)
|
||||
fmt.Println("Update metadata wiped, launching qTox")
|
||||
launchqtox := exec.Command("/usr/bin/open", "-b", "im.tox.qtox")
|
||||
launchqtox.Run()
|
||||
|
||||
} else {
|
||||
fmt.Println("Error: no user passed")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user