如何在C中将相对路径转换为绝对路径?

时间:2014-03-13 14:25:42

标签: c posix relative-path absolute-path

我正在尝试创建一个仅执行位于受限文件夹中的ruby脚本的suid应用程序。我试图使用realpath(3)做到这一点,但它只返回路径的第一段。以下是我的代码......

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>

#define SUEXEC_STR_LEN 2048
#define RUBY_APP "/usr/bin/ruby"
#define DIRECTORY_SEPARATOR "/"

static void safepath(const char *path_in, char * path_out, int outlen) {
    realpath(path_in, path_out);
}

int main ( int argc, char *argv[] )
{
    char cmd[SUEXEC_STR_LEN];
    char path_out[SUEXEC_STR_LEN];
    char path_in[SUEXEC_STR_LEN];

    char *cp = &cmd[0];

    strncpy(cp, RUBY_APP, SUEXEC_STR_LEN - 1);

    strncpy(path_in, DIRECTORY_SEPARATOR, SUEXEC_STR_LEN - 1);
    strncat(path_in,argv[1],SUEXEC_STR_LEN - 1);

    safepath(path_in,path_out,SUEXEC_STR_LEN - 1);

    printf("path_in=%s path_out=%s\n",path_in,path_out);

    setuid( 0 );
    // system( cmd );

    return 0;
}

这是我得到的结果的一个例子

root@server01:/root/src# ./a.out foo/bar/../test
path_in=/foo/bar/../test path_out=/foo

这是我想要的结果

root@server01:/root/src# ./a.out foo/bar/../test
path_in=/foo/bar/../test path_out=/foo/test

3 个答案:

答案 0 :(得分:1)

您应该检查realpath()的返回值。如其man page

中所述
  

返回值
  如果没有错误,则realpath()返回指向resolved_pa​​th的指针。

     

否则返回NULL指针,并且数组resolved_pa​​th的内容未定义。全局变量errno设置为指示错误。

同样在其手册页的错误部分,

  

ENOENT指定的文件不存在。

因此,如果文件系统中确实没有/foo/testrealpath()应返回NULL并且输出未定义。

答案 1 :(得分:1)

所以,这是工作草图,了解如何在 Linux 上的 C 中进行操作。这是一个快速的黑客,我不代表示范代码,效率等。它(ab)使用PATH_MAX,使用“坏”字符串函数,并可能泄漏内存,吃你的猫,并有角落案件那段错误等等当它它破坏时,你可以保留这两个部分。

基本思想是通过给定的路径,使用“/”作为分隔符将其分解为“单词”。然后,浏览列表,将“单词”推入堆栈,但忽略如果为空或“。”,然后弹出“...”,然后通过从底部开始序列化堆栈并在其间累积带有斜杠的字符串

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <linux/limits.h>

typedef struct stack_s {
    char *data[PATH_MAX];
    int   top;
} stack_s;

void stack_push(stack_s *s, char *c) {
    s->data[s->top++] = c;
}

char *stack_pop(stack_s *s) {
    if( s->top <= 0 ) {
        return NULL;
    }
    s->top--;
    return s->data[s->top];
}

// DANGER! DANGER! Returns malloc()ed pointer that you must free()
char *stack_serialize(stack_s *s) {
    int i;
    char *buf;
    int len=1;

    for(i=0; i<s->top; i++) {
        len += strlen(s->data[i]);
        len++; // For a slash
    }
    buf = malloc(len);
    *buf = '\0';
    for(i=0; i<s->top-1; i++) {
        strcat(buf, s->data[i]);
        strcat(buf, "/");
    }
    strcat(buf, s->data[i]);
    return buf;
}

// DANGER! DANGER! Returns malloc()ed pointer that you must free()
char *semicanonicalize(char *src) {
    char *word[PATH_MAX] = {NULL};
    int   w=0;
    int   n_words;

    char *buf;
    int   len;
    char *p, *q;

    stack_s dir_stack = {{NULL},0};

    // Make a copy of the input string:
    len = strlen(src);
    buf = strdup(src);

    // Replace slashes with NULs and record the start of each "word"
    q = buf+len;
    word[0]=buf;
    for(p=buf,w=0; p<q; p++) {
        if(*p=='/') {
            *p = '\0';
            word[++w] = p+1;
        }
    }
    n_words=w+1;

    // We push w[0] unconditionally to preserve slashes and dots at the
    // start of the source path:
    stack_push(&dir_stack, word[0]);

    for(w=1; w<n_words; w++) {
        len = strlen(word[w]);
        if( len == 0 ) {
            // Must've hit a double slash
            continue;
        }
        if( *word[w] == '.' ) {
            if( len == 1 ) {
                // Must've hit a dot
                continue;
            }
            if( len == 2 && *(word[w]+1)=='.' ) {
                // Must've hit a '..'
                (void)stack_pop(&dir_stack);
                continue;
            }
        }
        // If we get to here, the current "word" isn't "", ".", or "..", so
        // we push it on the stack:
        stack_push(&dir_stack, word[w]);
    }

    p = stack_serialize(&dir_stack);
    free(buf);
    return p;
}


int main(void)
{
    char *in[] = { "/home/emmet/../foo//./bar/quux/../.",
                   "../home/emmet/../foo//./bar/quux/../.",
                   "./home/emmet/../foo//./bar/quux/../.",
                   "home/emmet/../foo//./bar/quux/../."
    };
    char *out;
    for(int i=0; i<4; i++) {
        out = semicanonicalize(in[i]);
        printf("%s \t->\t %s\n", in[i], out);
        free(out);
    }
    return 0;
}

答案 2 :(得分:0)

这是我用来解决问题的代码。它可能还有一些漏洞,并没有检查outlen参数以避免段错误和其他丑陋,但它似乎完成了工作。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <linux/limits.h>

#define SUEXEC_STR_LEN 2048
#define RUBY_APP "/usr/bin/ruby"
#define DIRECTORY_SEPARATOR "/"
#define RUBY_EXT ".rb"

#define SERVICES_BASE_PATH "/path/to/ruby/services"

static inline int isDirSeparator(const char c) { return (c == '/' || c == '\\'); }

static void safepath(const char *path_in, char * path_out, int outlen)
{
    char *dirs[PATH_MAX];
    int depth = 0;
    char *dstptr = path_out;
    const char *srcptr = path_in;

    *dstptr++ = DIRECTORY_SEPARATOR[0];
    dirs[0] = dstptr;
    dirs[1] = NULL;
    depth++;

    while (1) {
        if ((srcptr[0] == '.') && isDirSeparator(srcptr[1])) {
            srcptr += 2;
        } else if (srcptr[0] == '.' && srcptr[1] == '.' && isDirSeparator(srcptr[2])) {
            if (depth > 1) {
                dirs[depth] = NULL;
                depth--;
                dstptr = dirs[depth-1];
            } else {
                dstptr = dirs[0];
            }
            srcptr += 3;
        } else if (srcptr[0] == '.' && srcptr[1] == '.' && srcptr[2] == 0) {
            if (depth == 1) {
                srcptr += 2;
            } else {
                depth--;
                dstptr = dirs[depth-1];
                srcptr += 2;
            }
        } else {
            while (!isDirSeparator(srcptr[0]) && srcptr[0]) {
                *dstptr++ = *srcptr++;
            }
            if (srcptr[0] == 0) {
                if (dstptr != dirs[0] && isDirSeparator(dstptr[-1])) {
                    dstptr[-1] = 0;
                }
                dstptr[0] = 0;
                return;
            } else if (isDirSeparator(srcptr[0])) {
                if (dstptr == dirs[0]) {
                    srcptr++;
                } else {
                    *dstptr++ = *srcptr++;
                    dirs[depth] = dstptr;
                    depth++;
                }
                while (isDirSeparator(srcptr[0]) && srcptr[0]) {
                    srcptr++;
                }
            } else {
                path_out[0] = 0;
                return;
            }
        }
    }
}

int main ( int argc, char *argv[] )
{
    int ret;
    char cmd[SUEXEC_STR_LEN];
    char path_out[SUEXEC_STR_LEN];
    char path_in[SUEXEC_STR_LEN];

    char *cp = &cmd[0];


    if (argc < 2) {
        fprintf(stderr,"usage: %s <service>\n",argv[0]);
        return 1;
    }
    strncpy(cp, RUBY_APP, SUEXEC_STR_LEN - 1);

    strncpy(path_in, DIRECTORY_SEPARATOR, SUEXEC_STR_LEN - 1);
    strncat(path_in,argv[1],SUEXEC_STR_LEN - 1);

    safepath(path_in,path_out,SUEXEC_STR_LEN - 1);

    //printf("path_in=%s path_out=%s\n",path_in,path_out);

    strncat(cmd," ",SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));

    strncat(cmd,SERVICES_BASE_PATH,SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));
    strncat(cmd,path_out,SUEXEC_STR_LEN - (1+sizeof(RUBY_EXT)));
    strncat(cmd,RUBY_EXT,SUEXEC_STR_LEN - 1);

    setuid( 0 );
    ret = system( cmd );
    if (ret == -1) {
        return ret;
    }
    ret =  WEXITSTATUS(ret);
    return ret;
}