Programmer’s Toolbox - Dateien, Teil 14: Zwei Module zur Benutzerverwaltung mit Paßwortschutz

In Teil 13 der Programmer’s Toolbox wurden einige einfache Verschlüsselungsverfahren vorgestellt, die alle einen bedauerlichen Nachteil aufweisen: Die mit ihnen verschlüsselten Botschaften sind leicht zu knacken. In der heutigen Folge wird ein Modul abgelistet, das eine komplexere Verschlüsselung vornimmt. Danach wird ein weiteres Modul implementiert, das eine Benutzerverwaltung ermöglicht.

Der Data Encryption Standard

Die niedrige Verschlüsselungssicherheit der im letzten Teil vor gestellten Verfahren hat mehrere Ursachen:

Bei einem sichereren Verfahren kommt es mithin darauf an, möglichst bitorientiert zu arbeiten. Dadurch wird eine eventuell vorliegende Struktur (z.B. die Struktur Byte) aufgebrochen. Außerdem sollte der zugrundeliegende Algorithmus möglichst komplex sein.

Ein solcher komplexer, bitorientierter Algorithmus wurde im Jahre 1977 von dem U.S. National Bureau of Standards vorgestellt. Es handelt sich dabei um den sogenannten Data Encryption Standard (DES). Der DES-Algorithmus verarbeitet jeweils Blöcke von 64 Bits. Die Bits eines Blocks werden in einem sehr komplizierten Verfahren miteinander verknüpft, wobei anstelle von einfachen Bit-Permutationen die Informationen über den kompletten Blockbereich verteilt werden. Auf die genauen inneren Details dieses „wirren“ Algorithmus’ und seiner nicht minder „wirren“ Implementierung (Implementierung in Listing 3.4, Header in Listing 3.5) möchte ich an dieser Stelle nicht eingehen. Es sei auf Dewdney (Spektrum der Wissenschaft 1/89, Seiten 6-9) verwiesen. Stattdessen möchte ich gleich zum Modul für die Benutzerverwaltung (USERMAIN) übergehen.

Ein Modul zur Benutzerverwaltung

Das Modul für die Benutzerverwaltung soll folgende Aufgaben erfüllen:

Das Modul USERMAIN benötigt entsprechend folgende Funktionen:

load_user_data
Laden der Paßwortdatei und Aufbau der programmintemen Darstellung der Benutzerdatensätze

find_user
Durchsuchen der programmintemen Darstellung der Benutzerdatensätze nach den Daten eines bestimmten Benutzers

insert_replace_user
Ersetzen bzw. Einfügen eines Benutzerdatensatzes in die interne Darstellung

delete_user
Löschen eines Benutzerdatensatzes, der durch den Benutzernamen identifiziert wird.

set_modification_bit
Benachrichtigung des Moduls USERMAIN, daß modulextern etwas an der internen Darstellung modifiziert worden ist. (Trotz einiger Lektionen hinsichtlich strukturierter Programmierung läßt sich der Hacker in mir einfach nicht unterdrücken.)

save_user_data
Speichern der programminternen Darstellung in der Paßwortdatei, um Änderungen, die mit Hilfe der Funktionen insert_replace_user oder delete_user vorgenommen wurden, persistent zu machen.

Diese sechs Funktionen sind innerhalb von Listing 3.6 implementiert. Wie gesagt, erfolgt die Implementierung der internen Darstellung mit Hilfe einer linearen Liste (Listing 3.6, Zeilen 37-46). Lineare Listen wurden innerhalb dieser Serie bereits an früherer Stelle verwendet. Ich erspare mir und Ihnen daher weitere Ausführungen zu diesem Thema.

Neben den Funktionen zur Verwaltung der Benutzerdatensätze weist Listing 3.6

eine kleine Hilfsfunktion auf: Die Funktion input (Zeilen 285-311) erlaubt die Eingabe einer Zeichenkette von der Konsole. Dabei kann zwischen Eingabe mit und ohne Echo differenziert werden.

Listing 3.7 enthält die Header-Datei des gerade beschriebenen Moduls.

Vorausschau

Mit den Modulen CRYPT und USERMAIN können wir die Benutzerverwaltung mit Paßwortschutz in der nächsten und letzten Folge fertigstellen. Im einzelnen werden dabei folgende Kommandos implementiert:

MKUSER - Anlegen eines Benutzerdatensatzes

RMUSER - Löschen eines Benutzerdatensatzes

PASSWD - Ändern von Paßwörtern

LOGIN - Einloggen in das System

/* (c) Maxon Computer 1991
 * listing 3.4, Datei   : crypt.c
 * Modul                : CRYPT — Verschlüsselung
 *                        nach dem DES
 * Modifikationsdatum   : 12-Dez-1990
 * Abhängigkeiten       : -
 */

/*
 * Datentypen   : BLOCK, ORDERING
 * Variable     : key
 *
 * Aufgabe      :
 *
 * Darstellung von Schlüssel- und Datenobjekten
 * («BLOCK» sowie Permutationsdaten («ORDERING» .
 */

typedef struct {
    unsigned char b_data[64];
} BLOCK;

typedef struct {
    unsigned char o_data[64J;
} ORDERING,

BLOCK key;

/*
 * Variablen    : ip, pi, swap, pc1, pc2, etr
 *                ptr, s_boxes, rots
 *
 * Aufgabe :
 *
 * Einige Permutationsdaten.
 */

ORDERING ip = {
    58, 50, 42, 34, 26, 18, 10, 2,
    60, 52, 44, 36, 28, 20, 12, 4,
    62, 54. 46, 38, 30, 22, 14, 6,
    64, 56, 48, 40, 32, 24, 16, 8,
    57, 49, 41, 33, 25, 17,  9. 1,
    59, 51, 43, 35, 27, 19, 11, 3,
    61, 53, 45, 37, 29, 21, 13, 5,
    63, 55, 47, 39, 31, 23, 15, 7
};

ORDERING pi = {
    40, 8, 48, 16, 56, 24, 64, 32,
    39, 7, 47, 15, 55, 23, 63, 31,
    38, 6, 46, 14. 54, 22, 62, 30,
    37, 5, 45, 13, 53, 21, 61. 29,
    36, 4, 44, 12, 52, 20, 60, 28,
    35, 3, 43, 11, 51, 19, 59, 27,
    34, 2, 42, 10, 50, 18, 58, 26,
    33, 1, 41, 9, 49, 17, 57, 25
};

ORDERING swap = {
    33, 34, 35, 36, 37, 38, 39, 40,
    41, 42, 43, 44, 45, 46, 47, 48.,
    49, 50, 51, 52, 53. 54, 55, 56,
    57, 58, 59, 60, 61, 62. 63, 64,
    1, 2, 3, 4, 5. 6, 7, 8,
    9, 10, 11, 12, 13, 14, 15, 16,
    17, 18, 19, 20, 21, 22, 23, 24,
    25, 26, 27, 28, 29, 30, 31, 32
};

ORDERING pc1 = {
    57, 49, 41, 33, 25, 17, 9,
    1, 58, 50, 42, 34. 26. 18,
    10, 2, 59, 51, 43, 35, 27,
    19, 11. 3, 60, 52, 44, 36,
    63, 55, 47, 39, 31, 23, 15,
    7, 62, 54, 46, 38. 30, 22,
    14, 6, 61, 53, 45, 37, 29,
    21, 13, 5, 28, 20, 12, 4
};

ORDERING pc2 = {
    14, 17, 11, 24, 1, 5,
    3, 28, 15, 6. 21, 10,
    23, 19, 12, 4, 26, 8,
    16. 7, 27, 20, 13, 2,
    41. 52, 31, 37, 47, 55,
    30. 40, 51. 45, 33, 48,
    44, 49, 39, 56, 34, 53,
    46, 42, 50, 36, 29, 32
};

ORDERING etr = {
    32, 1, 2, 3, 4, 5,
    4, 5, 6, 7, 8, 9,
    8, 9, 10. 11, 12, 13,
    12, 13, 14, 15, 16, 17,
    16, 17, 18, 19, 20, 21,
    20, 21. 22, 23, 24, 25,
    24, 25, 26, 27, 28, 29,
    28, 29, 30, 31, 32, 1
};

ORDERING ptr = {
    16, 7, 20, 21, 29, 12. 28, 17,
    1, 15, 23, 26, 5, 18. 31, 10.
    2, 8, 24, 14, 32, 27, 3, 9,
    19, 13, 30, 6, 22, 11, 4, 25
};

unsigned char s_boxes[8][64] = {
    {   14,  4, 13, 1, 2, 15, 11, 8,
         3, 10, 6, 12, 5, 9, 0, 7,
         0, 15, 7, 4, 14, 2, 13, 1,
        10,  6, 12, 11, 9, 5, 3, 8,
         4,  1, 14, 8, 13, 6, 2, 11,
        15, 12, 9, 7, 3, 10, 5, 0,
        15, 12, 8, 2, 4, 9, 1, 7,
         5, 11, 3, 14, 10, 0, 6, 13
    },

    {   15,  1, 8, 14, 6, 11, 3, 4,
         9,  7, 2, 13, 12, 0, 5, 10,
         3, 13, 4, 7, 15, 2, 8, 14,
        12,  0, 1, 10, 6, 9, 11, 5,
         0, 14, 7, 11, 10, 4, 13, 1,
         5,  8, 12, 6, 9, 3, 2, 15,
        13,  8, 10, 1, 3, 15, 4, 2,
        11,  6, 7, 12, 0, 5, 14, 9
    },

    {   10,  0, 9, 14, 6, 3, 15, 5,
         1, 13, 12, 7, 11, 4, 2, 8,
        13,  7, 0, 9, 3, 4, 6, 10,
         2,  8, 5, 14, 12, 11, 15, 1,
        13,  6, 4, 9, 8, 15, 3, 0,
        11,  1, 2, 12, 5, 10, 14, 7,
         1, 10, 13, 0, 6, 9, 8, 7,
         4, 15, 14, 3, 11, 5, 2, 12
    },

    {    7, 13, 14, 3, 0, 6, 9, 10,
         1,  2, 8, 5, 11, 12, 4, 15,
        13,  8, 11, 5, 6, 15, 0, 3,
         4,  7, 2, 12, 1, 10, 14, 9,
        10,  6, 9, 0, 12, 11, 7, 13,
        15,  1, 3, 14, 5, 2, 8, 4,
         3, 15, 0, 6, 10, 1, 13, 8,
         9,  4, 5, 11, 12, 7, 2, 14
    },

    {    2, 12, 4, 1, 7, 10, 11, 6,
         8,  5, 3, 15, 13, 0, 14, 9,
        14, 11, 2, 12, 4, 7, 13, 1,
         5,  0, 15, 10, 3, 9, 8, 6,
         4,  2, 1, 11, 10, 13, 7, 8,
        15,  9, 12, 5, 6, 3, 0, 14,
        11,  8, 12, 7, 1, 14, 2, 13,
         6, 15, 0, 9, 10, 4, 5, 3
    },

    {   12,  1, 10, 15,  9,  2,  6,  8,
         0, 13,  3,  4, 14,  7,  5, 11,
        10, 15,  4,  2,  7, 12,  9,  5,
         6,  1, 13, 14,  0, 11,  3,  8,
         9, 14, 15,  5,  2,  8, 12,  3,
         7,  0,  4, 10,  1, 13, 11,  6,
         4,  3,  2, 12,  9,  5, 15, 10,
        11, 14,  1,  7,  6,  0,  8, 13
    },

    {    4, 11,  2, 14, 15,  0,  8, 13,
         3, 12,  9,  7,  5, 10,  6,  1,
        13,  0, 11,  7,  4,  9,  1, 10,
        14,  3,  5, 12,  2, 15,  8,  6,
         1,  4, 11, 13, 12,  3,  7, 14,
        10, 15,  6,  8,  0,  5,  9,  2,
         6, 11, 13,  8,  1,  4, 10,  7,
         9,  5,  0, 15, 14,  2,  3, 12
    },

    {   13,  2,  8,  4,  6, 15, 11,  1,
        10,  9,  3, 14,  5,  0, 12,  7,
         1, 15, 13,  8, 10,  3,  7,  4,
        12,  5,  6, 11,  0, 14,  9,  2,
         7, 11,  4,  1,  9, 12, 14,  2,
         0,  6, 10, 13, 15,  3,  5,  8,
         2,  1, 14,  7,  4, 10,  8, 13,
        15, 12,  9,  0,  3,  5,  6, 11
    }

};

int rots[] = {
    1, 1, 2, 2, 2, 2, 2, 2,
    1, 2, 2, 2, 2, 2, 2, 1
};

/*
 * Funktionen   : transpose
 *
 * Parameter    : transpose(data, t, n);
 *                BLOCK *data;
 *                ORDERING *t;
 *                int n;
 *
 * Aufgabe      :
 *
 * Permutation des durch <data> referenzierten
 * Datenblocks der Länge <n> mit den durch <t>
 * referenzierten Permutationsdaten 
 */

void transpose(data, t, n)
BLOCK    *data;
ORDERING *t; 
int      n;
{   BLOCK x;

    x = *data; 
    while (n > 0) { 
        n—-;
        data->b_data[n] = x.b_data[t->o_data[n] -1];
    }
}

/*
 * Funktionen   : rotate
 *
 * Parameter    : rotate(key);
 *                BLOCK *key;
 *
 * Aufgabe      :
 *
 * Rotation des Schlüssels <key>. 
 */

void rotate (key)
BLOCK *key;
{   unsigned char *p; 
    unsigned char *ep; 
    int           data0,
                  data28;

    p = key->b_data; 
    ep = &(key->b_data[55]); 
    data0 = key->b_data[0]; 
    data28 = key->b_data[28]; 
    while (p < ep) {
        p++;
        *(p-1) = *p;
    }
    key->b_data[27] = data0; 
    key->b_data[55] = data28;
}

/*
 * Funktionen   : f
 *
 * Parameter    : f(i, key, a, x);
 *                int       i;
 *                BLOCK     *key,
 *                          *a,
 *                          *x;
 *
 * Aufgabe      :
 *
 * Durchführung der f-Funktion.
 */

ORDERING *EP = &etr;

void f(i, key, a, x) 
int     i;
BLOCK   *key,
        *a,
        *x;
{   BLOCK         e,
                  ikey,
                  y;
    int           k,
                  xb, 
                  ri;
    unsigned char *p,
                  *q,
                  *r;

    e = *a;
    transpose(&e, EP, 48); 
    for (k = rots[i]; k > 0; k-—) 
        rotate(key); 
    ikey = *key;
    transpose(&ikey, &pc2, 48); 
    p = &(y,b_data[48]); 
    q = &(e.b_data[48]); 
    r = &(ikey,b_data[48]); 
    while (p > y.b_data)
        *-—p = *—-q ^ *—-r; 
    q = x->b_data; 
    for (k = 0; k < 8; k++) { 
        ri = *p++ << 5; 
        ri += *p++ << 3; 
        ri += *p++ << 2; 
        ri += *p++ << 1; 
        ri += *p++; 
        ri += *p++ << 4;

        xb = s_boxes[k][ri];

        *q++ = (xb >> 3) & 1;
        *q++ = (xb >> 2) & 1;
        *q++ = (xb >> 1) & 1;
        *q++ = (xb & 1) ;
    }
    transpose(x, &ptr, 32);
}

/*
 * Funktionen   : encrypt
 *
 * Parameter    : encrypt(blck);
 *                BLOCK *blck;
 *
 * Aufgabe      :
 *
 * Zentrale Verschlusselungsroutine.
 */

static void setkey(k) 
char *k;
{   key = *((BLOCK *) k);
    transpose(&key, &pcl, 56);
}

void encrypt (blck) 
char *blck;
{   BLOCK *p = (BLOCK *) blck, 
          b, 
          x;
    int   i,
          j,
          k;

    transpose(p, &ip, 64); 
    for (i = 15; i>= 0; i-—) { 
        j = 15 - i; 
        b = *p;
        for (k = 31; k >= 0; k-—)
            p->b_data[k] = b.b_data[k + 32]; 
        f(j, &key, p, &x); 
        for (k = 31; k >= 0; k—-)
            p->b_data[k+32] = b.b_data[k] ^ x.b_data[k];
    }
    transpose(p, &swap, 64); 
    transpose(p, &pi, 64);
}

/*
 * Funktionen   : crypt
 *
 * Parameter    : erg = crypt (passwd, salt);
 *                char *erg,
 *                     *passwd,
 *                     *salt;
 *
 * Aufgabe      :
 *
 * Die Verschlüsselung von PaPwortera mit dem
 * DES. Als erster Parameter wird das PaPwort
 * übergeben. Als zweiter Parameter ein mindestens
 * zwei Zeichen umfassender Zufallsstring.
 * Als Ergebnis erhalt man das verschlüsselte
 * Paßwort, dem die ersten beiden Zeichen des
 * Zufallsstrings vorangestellt sind.
 */

char *crypt(pw, salt) 
char *pw,
     *salt;
{   char     pwb[66],
             result[16],
             *p = pwb, 
             c;
    ORDERING new_etr; 
    int      i,
             j,
             t,
             temp;

    while (*pw && p < &pwb[64]) {
        j = 7;
        while (j—-)
            *p++ = (*pw >> j) & 01; 
        pw++;
        *p++ = 0;
    }

    while (p < &pwb[64])
        *p++ = 0;

    setkey(p = pwb);

    while (p < &pwb[66))
        *p++ = 0;

    new_etr = etr;
    EP = &new_etr; 
    for (i = 0; i < 2; i++) { 
        c = *salt++; 
        result[i] = c; 
        if ( c > 'Z')
            c -= 6 + 7 + '.'; 
        else if ( c > '9') 
            c -= 7 + '.';
        else
            c -= '.'; 
        for (j = 0; j < 6; j++) { 
            if ((c >> j) & 01) { 
                t = 6 * i + j; 
                temp = new_etr.o_data[t]; 
                new_etr.o_data[t] = new_etr.o_data[t + 24]; 
                new_etr.o_data[t + 24] = temp;
            }
        }
    }

    if (result[1] = 0)
        result[1] = result[0];

    for (i = 0; i < 25; i++) 
        encrypt(pwb);
    EP = &etr;

    p = pwb;
    pw = result + 2; 
    while (p < &pwb[66]) { 
        o = 0; 
        j = 6;
        while (j—-) { 
            c <<= 1;
            c |= *p++;
        }
        c += '.';
        if (c > '9') 
            c += 7; 
        if (c > 'Z')
            c += 6;
        *pw++ = c;
    }
    *pw = 0; 
    return(result);
}

/* * Listing 3.5, Datei : crypt.h * Modul : CRYPT - Verschlüsselung * nach dem DES * Modifikationsdatum : 12-Dez-199G * Abhängigkeiten : - */ extern char *crypt{);

/* (c) MAXON Computer 1991 * Listing 3.6, Datei : usermain.c * Modul : USERMAIN - Benutzer- * Verwaltung * Modifikationsdatum : 27-Dez-1990 * Abhängigkeiten : stdio.h, string.h, ctype.h * osbind.h, local.h */ #include <stdio.h> #include <string.h> #include <ctype.h> #include <osbind.h> #include "local.h" /* * Konstante : PASSWD_FILENAME * * Aufgabe : * * Enthält den Dateiname in dem sich die * Benutzerdaten befinden. */ #define PASSWD_FILENAME "\\BIN\\PASSWD" /* * Datentypen/ * Variablen : USER_LIST, USER_LIST_NODE/ * user_list, modified * * Aufgabe : * * Interne Darstellung der Benutzerdaten */ typedef struct user_list_node *USER_LIST; typedef struct user_list_node { char *user, *passwd, *shell; USER_LIST next; } USER_LIST_NQDE; static USER_LIST user_list = NULL; static BOOLEAN modified = FALSE; /* * Funktionen : load_user_data * * Parameter : load_user_data(); * * Aufgabe : * * Laden der Benutzerdaten aus der Datei * <PASSWD_FILENAME>. Der Aufruf dieser Funktion * ist die Voraussetzung für nachfolgende Zugriffe * mit <find_data>, <insert_replace_data> und * <delete_data> */ void load_user_data () { FILE *file, char user[100], passwd[100], shell[100]; USER_LIST work, new; if (user_list != NULL) return; file = fopen(PASSWD_FILENAME, "r"); if (file == NULL) return ; while (!feof(file)) { fscanf(file, "%s|%s|%s", user, passwd, shell); if (!feof(file)) { new = (USER_LIST)malloc(sizeof(USER_LIST_NODE)); new->user = malloc(strlen(user) + 1); strcpy(new->user, user); new->passwd = malloc(strlen(passwd)+1); strcpy(new->passwd, passwd); new->shell = malloc(strlen(shell) +1); strcpy(new->shell, shell); new->next = NULL; if (user_list == NULL) { user_list = new; work = new; } else { work->next = new; work = new; } } } fclose(file); } /* * Funktionen : find_user * * Parameter : list = find_user(user); * USER_LIST list; * char *user; * * Aufgabe : * * Durchsuchen der Benutzerdaten innerhalb der * <user_list> nach den Daten für den Benutzer * <user>. Bei erfolgreicher Suche wird ein * entsprechender Zeiger zurückgegeben. Ansonsten * wird der Wert NULL zuruckgegeben */ USER_LIST find_user(user) char *user; { USER_LIST work = user_list; while(work != NULL) { if (strcmp(user, work->user) == 0) return(work); work = work->next; } return(NULL); } /* * Funktionen : insert_replace_user * * Parameter : insert_replace_user (user, * passwd, shell); * char *user, * *passwd, * *shell; * * Aufgabe : * * Einfügen oder Ersetzen der Benutzerdaten für * den Benutzer <user>. */ void insert_replace_user(user, passwd, shell) char *user, *passwd, *shell; { USER_LIST list, new; list = find_user(user); if (list == NULL) { new = (USER_LIST)malloc(sizeof(USER_LIST_NODE)); new->user = malloc(strlen(user) + 1); strcpy(new->user, user); new->passwd = malloc(strlen(passwd) + 1); strcpy(new->passwd, passwd); new->shell = malloc(strlen(shell) + 1); strcpy(new->shell, shell); new->next = user_list; user_list = new; } else { free(list->passwd); free(list->shell); list->passwd = malloc(strlen(passwd) + 1); strcpy(list->passwd, passwd); new->shell = malloc(strlen(shell) + 1); strcpy(new->shell, shell); } modified = TRUE; } /* * Funktionen : delete_user * * Parameter : delete_user(user); * char *user; * * Aufgabe : * * Löschen der Daten des Benutzers <user>. */ void delete_user(user) char *user; { USER_LIST prework = NULL, work = user_list, old; while (work != NULL) { if (strcmp(work->user, user) == 0) { old = work; if (prework == NULL) user_list = work->next; else prework->next = work->next; free(work->user); free(work->passwd); free(work->shell); free((char *)work); modified = TRUE; return ; } prework = work; work = work->next; } } /* * Funktionen : set_modification_bit * * Parameter : set_modification_bit(); * * Aufgabe : * * Falls eine direkte externe Manipulation von * Benutzerdaten vorgenommen worden ist, dann * kann dies dem Modul USERMAIN mit * <set_modification_bit> mitgeteilt werden */ void set_modification_bit() { modified = TRUE; } /* * Funktionen : save_user_data * * Parameter : save_user_data(); * * Aufgabe : * * Falls mit den vorangehenden Funktionen * Änderungen an den Benutzerdaten vorgenommen * worden sind, dann werden diese Änderungen * durch Aufruf von <save_user_data> gespeichert. */ void save_user_data() { FILE *file, USER_LIST old; if (!modified) return ; file = fopen(PASSWD_FILENAME, "w"); if (file == NULL) return ; while (user_list != NULL) { fprintf(file, "%s | %s | %s\n", user_list->user, user_list->passwd, user_list->shell); old = user_list; user_list = user_list->next; free(old->user); free(old->passwd); free(old->shell); free((char *)old); } fclose(file); modified = FALSE; } /* * Funktionen : input * * Parameter : input(prompt, erg, echo), * char *prompt, * *erg; * BOOLEAN echo; * * Aufgabe : * * Einlesen einer Zeile mit oder ohne <echo>. * Mit <prompt> wird der Text übergeben, der als * Zeileninformation ausgegeben wird innerhalb * von <erg> wird der Zeileninhalt zurückgegeben */ #define MAXSTR 100 void input(prompt, erg, echo) char *prompt, *erg; BOOLEAN echo; { int i = 0; char c; printf("%s ", prompt); fflush(stdout); do { if (echo) c = Cconin(); else c = Cnecin(); if (c == '\r') { while (i < MAXSTR) erg[i++] = 0; printf("\n"); break; } else if (isprint(c) && c != ' ') erg[i++] = c; } while(TRUE); if (strlen(erg) == 0) strcpy(erg, "ohne"); }

/* * Listing 3.7, Datei : usermain.h * Modul : USERMAIN - Benutzer- * Verwaltung * Modifikationsdatum : 27-Dez-1990 * Abhängigkeiten : stdio.h, string.h,local.h */ typedef struct user_list_node *USER_LIST; typedef struct user_list_node { char *user, *passwd, *shell; USER_LIST next; } USER_LIST_NODE; extern void load_user_data(); extern USER_LIST find_user(); extern void insert_replace_user(), delete_user(), set_modification_bit(), save_user_data(), input();

Dirk Brockhaus
Links

Copyright-Bestimmungen: siehe Über diese Seite
Classic Computer Magazines
[ Join Now | Ring Hub | Random | << Prev | Next >> ]