strcpy() 由于未知原因抛出分段错误

问题描述 投票:0回答:1

我正在尝试学习 C 语言的套接字编程,我想创建一个管理聊天室的服务器,客户端可以连接该聊天室并向其他人发送消息

这是服务器代码: (您可以跳过它来查看代码重要部分的片段)

/* Server that manages a chat room */

// include necessary libraries
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netdb.h>

// definitions

// function declarations
void printaddrs(struct addrinfo *res);
void printusage();
int isdig(char c);
int isaddr(char addr[INET6_ADDRSTRLEN]);
int isport(char port[6]);
char *strin(FILE *stream);
int getaddrfamily(char addr[INET6_ADDRSTRLEN]);

int main(int argc, char *argv[]) {
    // argv[1] -> address for the server to bind to, if it is 0, print a list of addrs with getaddrinfo()
    // argv[2] -> port for the server to bind to

    // ~~~~~~~~~~~~ Argument validity checking - Start~~~~~~~~~~~~
    if (argc < 3) {
        printf("Not enough arguments\n\n");
        printusage();
        return 1;
    }
    if (argc > 3) {
        printf("Too many arguments\n\n");
        printusage();
        return 1;
    }
    if (strlen(argv[1]) > INET6_ADDRSTRLEN) {
        printf("IP address too long\n");
        return 1;
    }
    if (isaddr(argv[1]) == 0) { //  is not a valid IPv4 or IPv6 address
        printf("Invalid <ip addr> argument\n\n");
        printusage();
        return 1;
    }
    if (strlen(argv[2]) > 6) {
        printf("Port number too long\n");
        return 1;
    }
    if (isport(argv[2]) == 0) { //
        printf("Invalid <port> argument\n\n");
        printusage();
        return 1;
    }
    if (strcmp(argv[2], "0") == 0) {
        printf("Argument <port> cannot be 0\n");
        return 1;
    }
    if (strtol(argv[2], NULL, 10) < 1024) {
        printf("CAUTION!\nBe cautious when using reserved port numbers\n");
    }
    // ~~~~~~~~~~~~ Argument validity checking - End ~~~~~~~~~~~~

    struct addrinfo hints, *res;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC; // Use both IPv4 and IPv6
    hints.ai_socktype = SOCK_STREAM; // Use TCP/IP (not UDP)

    char addrstr[INET6_ADDRSTRLEN] = "";
    if (strcmp(argv[1], "0") == 0) { // User entered '0' as the first argument
        int status = getaddrinfo(NULL, argv[2], &hints, &res);
        if (status != 0) {
            printf("getaddrinfo(): error: %s\n", gai_strerror(status));
            return 1;
        }
        printaddrs(res);
        printf("Select an address to bind to: ");
        char *id = strin(stdin);

        int count = 0;
        for (; res != NULL; res = res->ai_next) {
            if (strtol(id, NULL, 10) == count) {
                if (res->ai_family == AF_INET) {
                    struct sockaddr_in *ipv4;
                    ipv4 = (struct sockaddr_in *)res->ai_addr;
                    inet_ntop(res->ai_family, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
                } else if (res->ai_family == AF_INET6) {
                    struct sockaddr_in6 *ipv6;
                    ipv6 = (struct sockaddr_in6 *)res->ai_addr;
                    inet_ntop(res->ai_family, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
                } else {
                    printf("error: Choosing an address of an invalid address family is not allowed\n");
                    printf("Terminating...\n");
                    free(id);
                    freeaddrinfo(res);
                    return 1;
                }
                break;
            }
            count++;
        }
        free(id);
        if (res == NULL) {
            printf("error: Couldn't find entered id\n");
            freeaddrinfo(res);
            return 1;
        }
    } else { // User entered a specific IP address
        strncpy(addrstr, argv[1], 4);
        int addrfamily = getaddrfamily(addrstr);
        if (addrfamily == AF_INET) {
            struct sockaddr_in *ipv4;
            inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
            res->ai_addr = (struct sockaddr *)ipv4;
            res->ai_family = AF_INET;
        } else if (addrfamily == AF_INET6) {
            struct sockaddr_in6 *ipv6;
            printf("check1\n");
            inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
            printf("check2\n");
            res->ai_addr = (struct sockaddr *)ipv6;
            res->ai_family = AF_INET6;
        } else {
            printf("error: Invalid address\n");
            freeaddrinfo(res);
            return 1;
        }
    }
    printf("Server address: %s\n", addrstr);
    printf("Server port: %s\n", argv[2]);

    printf("Success!\nTerminating...\n");
    freeaddrinfo(res);
    return 0;
}

// ~~~~~~~~~~~~ Function definitions ~~~~~~~~~~~~

// prints available addresses to possibly bind to
void printaddrs(struct addrinfo *res) {
    int id = 0;
    char addrstr[INET6_ADDRSTRLEN];
    char ipver[30];
    for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
        if (p->ai_family == AF_INET) {
            struct sockaddr_in *ipv4;
            ipv4 = (struct sockaddr_in *)p->ai_addr;
            inet_ntop(AF_INET, &(ipv4->sin_addr), addrstr, sizeof(addrstr));
            strcpy(ipver, "ipv4");
        } else if (p->ai_family == AF_INET6) {
            struct sockaddr_in6 *ipv6;
            ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            inet_ntop(AF_INET6, &(ipv6->sin6_addr), addrstr, sizeof(addrstr));
            strcpy(ipver, "ipv6");
        } else {
            strcpy(addrstr, "error: Invalid address family");
            strcpy(ipver, "error: Invalid address family");
        }

        printf("%d|%s\t%s\n", id, ipver, addrstr);
        id++;
    }
}

// prints the 'usage:' message
void printusage() {
    printf("usage:\n./server <ip addr> <port>\n");
    printf("<ip addr> - IPv4 or IPv6 address for the server to bind to (only dots or colons allowed)\n");
    printf("<port> - Port number for the server to bind to\n");
}

// Returns 1 if character c is a digit (0-9)
int isdig(char c) {
    switch(c) {
        case '0':
            return 1;
        case '1':
            return 1;
        case '2':
            return 1;
        case '3':
            return 1;
        case '4':
            return 1;
        case '5':
            return 1;
        case '6':
            return 1;
        case '7':
            return 1;
        case '8':
            return 1;
        case '9':
            return 1;
        default:
            return 0;
    }
}

// Returns 1 if addr is a valid IPv4 or IPv6 address, otherwise returns 0
int isaddr(char addr[INET6_ADDRSTRLEN]) {
    int isipv4 = 0, isipv6 = 0;
    for (int i = 0; i < strlen(addr); i++) {
        // If both dots and colons are present in addrm return 0
        if (isipv4 && isipv6)
            return 0;
        if (addr[i] == '.')
            isipv4 = 1;
        else if (addr[i] == ':')
            isipv6 = 1;
        // if character is something other than a digit, dot or colon
        if (isdig(addr[i]) == 0 && addr[i] != '.' && addr[i] != ':')
            return 0;
    }
    return 1;
}

// Returns 1 if number(5 digits) is a valid port number, returns 0 if it's not
int isport(char port[6]) {
    for (int i = 0; i < strlen(port); i++) {
        if (isdig(port[i]) == 0)
            return 0;
    }
    return 1;
}

// Takes string input from FILE *stream - !!! result must be free()'d !!!
char *strin(FILE *stream) {
    char *str = NULL;
    size_t size = 0;

    int c;
    int i = 0;
    while (1) {
        c = getc(stream);
        if (stream != stdin) {
            if(c == EOF)
                break;
        } else {
            if(c == EOF || c =='\n')
                break;
        }
        size++;
        str = realloc(str, size * sizeof(char));
        *(str + i) = c;
        i++;
    }
    if (size == 0) {
        return NULL;
    }
    str[size] = '\0';
    return str;
}

// Returns the address family of a given valid IP address, or -1 if no dots or colons are present
int getaddrfamily(char addr[INET6_ADDRSTRLEN]) {
    for (int i = 0; i < strlen(addr); i++) {
        if (addr[i] == '.')
            return AF_INET;
        else if (addr[i] == ':')
            return AF_INET6;
    }
    return -1;
}

但这才是重要的部分:

else { // User entered a specific IP address
        strcpy(addrstr, argv[1]);
        int addrfamily = getaddrfamily(addrstr);
        if (addrfamily == AF_INET) {
            struct sockaddr_in *ipv4;
            inet_pton(AF_INET, addrstr, &(ipv4->sin_addr));
            res->ai_addr = (struct sockaddr *)ipv4;
            res->ai_family = AF_INET;
        } else if (addrfamily == AF_INET6) {
            struct sockaddr_in6 *ipv6;
            printf("check1\n");
            inet_pton(AF_INET6, addrstr, &(ipv6->sin6_addr));
            printf("check2\n");
            res->ai_addr = (struct sockaddr *)ipv6;
            res->ai_family = AF_INET6;
        } else {
            printf("error: Invalid address\n");
            freeaddrinfo(res);
            return 1;
        }
    }

如果用户输入 0 作为第一个参数,则会打印出可能的地址列表,用户可以选择一个,但我希望可以指定一个地址作为参数,但是当我尝试复制它时 (argv [1]) 到 addrstr 字符串中,它会抛出分段错误...

我尝试使用 strncpy 代替,得到了相同的结果,但是我注意到,当我只指定复制 3 个字符(在 127.0.0.1 的情况下,仅复制“127”部分)时,它的工作方式就像魅力一样,但是当我只指定复制 3 个字符时,它会再次抛出分段错误我想复制4个字符... 难道这个点是这里的问题吗?

TL;博士:

当我尝试将参数

argv[1]
复制到之前初始化为
addrstr
的字符串
char addrstr[INET6_ADDRSTRLEN] = "";
时,它会抛出分段错误。

c segmentation-fault strcpy
1个回答
0
投票

strncpy(addrstr, argv[1], 4);
行中,您最多复制argv[1]
addrstr
的4个字符。

strncpy()

的文档说:

如果源长度大于 num,则不会在目标末尾隐式附加空字符。因此,在这种情况下,destination 不应被视为以 null 结尾的 C 字符串(这样读取会溢出)。

您的

argv[1]

 可能长于 4 个字符(IPv4 地址是由点分隔的四个数字组成的序列,每个数字表示为十进制数字序列;这是很多字符!)。那么, 
/0
 数组内将不会有 
addrstr
 终止符(只有参数的前四个字节,以及一些垃圾)。然后,将数组(“未终止”字符串)传递给 
getaddrfamily()
,它调用 
strlen()
,它查找 
/0
,但没有找到它......并继续查找,超出分配的大小数组。迟早(如果越早越好),这会触发“分段错误”。

© www.soinside.com 2019 - 2024. All rights reserved.