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

3
我试图创建一个SUID应用程序,只能执行位于受限文件夹中的Ruby脚本。我已经尝试使用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

1
我认为这里的问题在于realpath()期望路径实际存在,因为它还宣传它解析符号链接,如果它仅仅操作字符串,那么它不可能做到。 - Emmet
@Emmet,你说得对。那么我应该使用什么? - Ralph Ritoch
请自行进行字符串操作。如何获取不存在的文件或目录的绝对路径? - timrau
1
@RalphRitoch:看看我下面的答案。编写一个可工作的草图并不比解释它需要更长的时间。 - Emmet
3个回答

2
您需要检查realpath()的返回值。如其手册页所述,

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

否则它将返回一个空指针,并且 resolved_path 数组的内容是未定义的。全局变量 errno 被设置为指示错误。

此外,在其手册页中的 ERRORS 部分,

ENOENT:命名的文件不存在。

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

这很有帮助,但只是揭示了realpath()无法解析不存在的文件。它并没有解决当文件不存在时将相对路径转换为绝对路径的问题。 - Ralph Ritoch
@RalphRitoch "只能执行位于受限文件夹中的Ruby脚本"——如果文件不存在,那这个点就毫无意义了,所以我认为realpath()应该可以解决问题,除非你有很多子目录,而且路径超过了PATH_MAX字符(如果我没记错,当前版本是1024)。如果realpath()没有返回正确的完整文件名,那么您可能还有其他问题。手动解析路径的问题在于您会完全忽略可能存在的软链接。 - Alexis Wilke

1
这里是一个关于如何在Linux上使用C语言实现的工作原型。这只是一个快速的hack,不代表是最佳代码或高效的代码等等。它滥用了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;
}

我喜欢这种解决问题的逻辑方法。我现在使用的hack几乎无法阅读。不过,出于档案目的,我会发布它。 - Ralph Ritoch

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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接