1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge commit '8934a11dbffce6b0611d97d5748668a5cdb8ba8a' as 'osx/updater'

This commit is contained in:
Sean Qureshi 2014-12-16 02:51:14 -08:00
commit 1e2e482c68
No known key found for this signature in database
GPG Key ID: 13D2043169D25DF4
4 changed files with 442 additions and 0 deletions

23
osx/updater/.gitignore vendored Normal file
View 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
osx/updater/README.md Normal file
View 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
osx/updater/qtox_sudo.m Normal file
View 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
osx/updater/updater.go Normal file
View 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")
}
}
}