-1

I want to write a code that will check the correctness of MAC address. Input should look for example like this D7:6E:F4:30:17:2B. Im thinking about using functions isdigit() and isupper(). Dont know how to make user avalaible to write " : " symbol and stop him from writting other symbols.

if(user input is 13:4F:60:AC:7O:DE)
    ... all good
if(user input is 14:a]:!o:0L)
    ... wrong input, retry

EDIT According to @Woodrow Barlow answer i've wrote that code:

int mac_address() 
    {   
        int is_valid = 1;
        printf("MAC ADDRESS:");
        fgets(mac_addr, sizeof(mac_addr), stdin); 
            if (mac_addr[sizeof(mac_addr) - 1] != '\0')
            {
                is_valid = 0;
            }
            else if (ether_aton(mac_addr) == NULL)
            {
                is_valid = 0;
                // input isn't recognizable as a MAC address
            }
            if (is_valid == 1)
            {
                system("clear");
                printf("valid!\n");
                printf("%s\n", mac_addr);
                return license_menu();
            }
            else {
                printf("invalid!\n");
                fflush(stdin);
                return 1;
            }
    }
jadamian
  • 33
  • 7
  • https://en.cppreference.com/w/c/string/byte/ispunct – Wolf Aug 20 '19 at 14:14
  • 1
    Your question is unclear, if you want to check for `:`, well check for `:`. – Jabberwocky Aug 20 '19 at 14:14
  • `:` **is** ASCII. I don't really understand your question. – Marco Bonelli Aug 20 '19 at 14:15
  • 5
    It's not clear to me what you're asking, or what your problem is. You can check that a `:` is a `:` by comparing it to `':'`. Also, ASCII is a text encoding. You're already likely using it. – Thomas Jager Aug 20 '19 at 14:15
  • 6
    `ispunct` checks for lots of other things you aren't interested in. You should use `isxdigit(ch) || ch==':'`. – Lundin Aug 20 '19 at 14:16
  • 3
    The `Y4` component is weird for a string of hex. – Jonathan Leffler Aug 20 '19 at 14:18
  • 3
    `if (sscanf(s, "%x:%x:%x:%x:%x:%x", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) != 6) { /* error*/ }` – Matt Aug 20 '19 at 14:25
  • What character set are you running against? `(c >= 'a' && c <= 'z')` is not guaranteed to work. – Andrew Henle Aug 20 '19 at 14:33
  • @Matt `char c = '\0'; if (sscanf(s, "%x:%x:%x:%x:%x:%x%c", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5], &c) != 6 || c != '\0') { /* error*/ }` if you want scanf to test if the string is strictly a MAC address (or you can use "strlen"). – Tom's Aug 20 '19 at 14:37
  • Of course there should be only letters from A to F and digits. I've wanted to focuse only on that ' : ' first. – jadamian Aug 20 '19 at 14:39
  • 1
    @Tom's will that last `%c` be read if it's the terminator character `'\0'`? – Marco Bonelli Aug 20 '19 at 14:40
  • Okay, then it's better `int c; if (sscanf(s, "%x:%x:%x:%x:%x:%x%c", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5], &c) != 6) { /* error*/ }` No need to set/check `c` value. – Matt Aug 20 '19 at 14:46
  • Also, `mac[0]..mac[5]` should be checked if they are in `0..255` range. – Matt Aug 20 '19 at 14:48
  • `unsigned char mac[6]; int i; for (i = 0; i < 17; i++) { if (i % 3 == 2 ? s[i] != ':' : !isxdigit(s[i])) break; } if (i == 17 && s[i] == '\0') { /* GOOD */ sscanf("%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]); } else { /* BAD */ }` – Ian Abbott Aug 20 '19 at 14:58
  • @MarcoBonelli No, it will no be read. Try to read an empty string and you will read nothing, not even '\0'. – Tom's Aug 20 '19 at 15:03
  • @Tom's ok, makes sense. – Marco Bonelli Aug 20 '19 at 15:04
  • @Matt It's out of habit that I set/check c, because there is case where it's important. Anyway, your scanf is flawed ("AAA:A:AAA:A:AAA:A" is accepted). It's simpler to manually parse the string like Ian Abbott show it. – Tom's Aug 20 '19 at 15:19
  • 2
    @Matt [The `scanf` functions are broken-as-specified and should never be used for anything](https://stackoverflow.com/questions/24302160/scanf-on-an-istream-object/24318630#24318630); please do not suggest their use, especially not to new C programmers. – zwol Aug 20 '19 at 15:23
  • @Tom's As I said there must be an additional check for a range. – Matt Aug 20 '19 at 15:24
  • @zwol `%x` is safe. And so the other arguments by the link do not apply in this particular case. – Matt Aug 20 '19 at 15:26
  • @zwol the answer you linked is about C++ and does not make any sense in this context. – Marco Bonelli Aug 20 '19 at 15:29
  • 1
    @Matt `char mac[6][3] = {{0}}; sscanf(s, "%2[0-9A-F]:%2[0-9A-F]:%2[0-9A-F]:%2[0-9A-F]:%2[0-9A-F]:%2[0-9A-F]%c", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], &c);` then you strlen all "mac" to see if their length is strictly 2. It's a little better, although I franckly prefere the plain for loop given how you have to tweak scanf and still be obligated to do ulterior check. – Tom's Aug 20 '19 at 15:31
  • @Matt `%x` is _not_ safe. It has undefined behavior on input overflow. If I remember correctly, the only `scanf` input formats that _don't_ have undefined behavior on invalid input are `%c` and `%NNNs` (where NNN is an explicit field width). – zwol Aug 20 '19 at 15:32
  • @MarcoBonelli That _question_ was about C++, but everything I said about `scanf` in that answer applies equally to C and C++. – zwol Aug 20 '19 at 15:33
  • @zwol By language standard `%x` is defined to be an equivalent of `strtoul`, therefore it cannot overflow in a conforming libc implementation. – Matt Aug 20 '19 at 15:41
  • `if( !(isalnum(x) || x == ':') ) { ... }` – S.S. Anne Aug 20 '19 at 15:51
  • 3
    @Matt The _syntax_ accepted by `%x` is defined to be the syntax accepted by `strtoul` with base 16, but the C standard never actually says that the conversion is performed as-if by `strtoul`, and [7.21.6.2p10](http://port70.net/~nsz/c/c11/n1570.html#7.21.6.2p10) says "if the result of the conversion cannot be represented in the [destination] object the behavior is undefined." The only viable interpretation of that sentence that I can see is, for any input where `strto*` would report overflow, the corresponding `scanf` specifiers have undefined behavior. – zwol Aug 20 '19 at 15:59
  • 1
    @Matt `"%x"` can overflow and is thus potential UB. `"%2x"` will not overflow, yet will accept inputs like questionable `" 12"`, `"-2"`, `"0"`. – chux - Reinstate Monica Aug 21 '19 at 01:30
  • Code could pedantically scan with `sscanf(s, "%1x%1x:%1x%1x:%1x%1x:%1x%1x:%1x%1x:%1x%1x%n", ...` and not risk UB. Then check the length for the correct offset via the `"%n"`. – chux - Reinstate Monica Aug 21 '19 at 01:38
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/198214/discussion-on-question-by-jadamian-is-there-an-exception-for-ispunct-in-c). – Samuel Liew Aug 21 '19 at 05:35

1 Answers1

2

The best way to parse a MAC address or check its validity is to use ether_aton. MAC addresses can come in many formats, and ether_aton can be relied on to parse them.

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <netinet/ether.h>

int main(int argc, const char *argv[])
{
    char mac_addr[64];

    while (true)
    {
        fgets(mac_addr, sizeof(mac_addr), stdin);
        if (mac_addr[sizeof(mac_addr) - 1] != '\0')
        {
            // input was too long for the buffer
            printf("invalid!\n");
        }
        else if (ether_aton(mac_addr) == NULL)
        {
            // input isn't recognizable as a MAC address
            printf("invalid!\n");
        }
        else
        {
            break;
        }
    }

    printf("valid!\n");
    printf("%s\n", mac_addr);
    return 0;
}

It sounds like you're checking one character at a time, that you want to reject an invalid character immediately without waiting for the full string of input, and that you specifically want to reject MAC addresses that have lowercase letters or that use separators other than a colon. Is that accurate? I will assume you have your own reasons for doing so.

The ispunct function is a red herring here. There is no reason to check whether a given character is a punctuation character; what you really want to know is whether it's a colon. specifically. you can compare them directly.

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <termios.h>

bool is_valid(char ch, int i)
{
    if ((i + 1) % 3 == 0)
    {
        return ch == ':';
    }
    else if (ch >= '0' && ch <= '9')
    {
        return true;
    }
    else if (ch >= 'A' && ch <= 'F')
    {
        return true;
    }

    return false;
}

int main(int argc, const char *argv[])
{
    struct termios old_tio, new_tio;
    const int max_len = strlen("00:00:00:00:00:00");
    char mac_addr[max_len + 1];
    char ch = '\0';
    int i = 0;
    int ret = 0;

    /* need to modify the terminal's underlying settings, because
     * by default STDIN is buffered to support backspace, etc.
     * by switching to non-buffered input, you lose a lot of basic
     * functionality like backspace.
     * that's why it's usually recommended to just read in the entire
     * line of text and then check if it's valid at the end.
     */
    tcgetattr(STDIN_FILENO, &old_tio);
    new_tio = old_tio;
    new_tio.c_lflag &=(~ICANON);
    tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);

    for (i = 0; i < max_len; i++)
    {
        ch = getchar();
        if (!is_valid(ch, i))
        {
            printf("\ninvalid!\n");
            ret = 1;
            goto exit;
        }
        mac_addr[i] = ch;
    }
    mac_addr[max_len] = '\0';

    printf("\nvalid!\n");
    printf("%s\n", mac_addr);

exit:
    /* this is important; need to reset the terminal
     * settings to their previous value before terminating.
     */
    tcsetattr(STDIN_FILENO,TCSANOW,&old_tio);
    return ret;
}
Woodrow Barlow
  • 8,477
  • 3
  • 48
  • 86
  • error in ```char mac_addr[max_len +1] { 0 };``` ----- variable-sized object may not be initialized, that i dont understand – jadamian Aug 21 '19 at 08:50
  • 1
    @jadamian i was really just trying to highlight the `is_valid` function; i assumed you already had your own user input code. but in any case i've updated the answer with a more complete example. – Woodrow Barlow Aug 21 '19 at 16:11
  • 1
    @jadamian i want to stress, though, that the top portion is really the best way to parse a MAC address. it's bizarre to actively reject MAC addresses that use lowercase letters, or a hyphen instead of a colon, etc. – Woodrow Barlow Aug 21 '19 at 16:19